diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d33656e0..8b9aefd74 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,6 +35,16 @@ jobs:
- run: bun run format:check
- run: bun run lint
- run: bun run typecheck
+ # `typecheck` is `bun run --filter '*' typecheck`, and each workspace's `tsc --noEmit` reads
+ # its own tsconfig, none of which includes a test tree. The suite is therefore the one part
+ # of this repository that no type-checker has ever read: bun erases types rather than
+ # checking them, so a test could pass an options object where a connection string belongs,
+ # or import a type from a path that does not exist and silently get `any`, and every check
+ # here stayed green. This reads `server/tests` under the server's own compiler options and
+ # fails on any error that is not in `server/tests/typecheck-baseline.txt` — the errors that
+ # were already there when the check was turned on. New ones are new defects.
+ - name: server/tests types (baselined)
+ run: bun run typecheck:tests
- name: Desktop version tracks the release version
run: bun desktop/scripts/desktop-version.ts check
# The chart's default image tag is its appVersion, and a release must move both together. They
@@ -349,9 +359,12 @@ jobs:
# pinned by the desktop lockfile rather than the root one.
- run: bun install --frozen-lockfile
working-directory: desktop
- # Not the db:migrate script: that one loads ../.env, which does not exist in CI. DATABASE_URL
- # comes from the job env instead, which drizzle.config.ts already reads.
- - run: bunx drizzle-kit migrate --config=drizzle.config.ts
+ # The same migrator a deployment runs, rather than `drizzle-kit migrate`: it is the one that
+ # refuses a database holding a stamp newer than the migrations it is about to walk past
+ # (server/src/db/migration-reach.ts), and a CI database that is ever reused rather than
+ # recreated is one of the places that can happen. Not the db:migrate script, which loads
+ # ../.env; that file does not exist in CI, and DATABASE_URL comes from the job env instead.
+ - run: bun scripts/migrate.ts
working-directory: server
# A passing job must include the expected test floor. Import-time failures can otherwise skip
# files before their tests are registered.
diff --git a/agent-mastra/bun.lock b/agent-mastra/bun.lock
new file mode 100644
index 000000000..7d608334e
--- /dev/null
+++ b/agent-mastra/bun.lock
@@ -0,0 +1,894 @@
+{
+ "lockfileVersion": 2,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "@openbot/agent-mastra",
+ "dependencies": {
+ "@ai-sdk/anthropic": "*",
+ "@ai-sdk/openai": "*",
+ "@mastra/core": "*",
+ "mastra": "*",
+ },
+ },
+ },
+ "packages": {
+ "@a2a-js/sdk-v0_3": ["@a2a-js/sdk@0.3.14", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "@bufbuild/protobuf": "^2.10.2", "@grpc/grpc-js": "^1.11.0", "express": "^4.21.2 || ^5.1.0" }, "optionalPeers": ["@bufbuild/protobuf", "@grpc/grpc-js", "express"] }, "sha512-F6Ew1AtPzCLhTn8h9yiqTe7DiDf6XVrSnq9V1YqSl9eWqPm6anMveTiKdCSb/76cW0YiJc24rNaUrVezFFHbqQ=="],
+
+ "@a2a-js/sdk-v1": ["@a2a-js/sdk@1.0.1", "", { "dependencies": { "jose": "^6.2.3", "uuid": "^11.1.0" }, "peerDependencies": { "@bufbuild/protobuf": "^2.10.2", "@grpc/grpc-js": "^1.11.0", "express": "^4.21.2 || ^5.1.0" }, "optionalPeers": ["@bufbuild/protobuf", "@grpc/grpc-js", "express"] }, "sha512-CJQdh3Wzwo8qIx5UUkSJ7+7BEI16PB+MXMHHNSmx8JQsQed2HlQgvx1ENOiKUfYA3PlcEvxIwv14dBblhDuPmw=="],
+
+ "@ai-sdk/anthropic": ["@ai-sdk/anthropic@4.0.75", "", { "dependencies": { "@ai-sdk/provider": "4.0.24", "@ai-sdk/provider-utils": "5.0.56" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4cj+Wf12DnKUAQsQFkk3W5hU3KYP8bGBjdNsji3fmLm1txrgDBQF3urpgltsik8lfUTa6nkKQYOmPrQgPXKMFw=="],
+
+ "@ai-sdk/openai": ["@ai-sdk/openai@4.0.87", "", { "dependencies": { "@ai-sdk/provider": "4.0.24", "@ai-sdk/provider-utils": "5.0.56" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dfadBgux+npOoaPmIP3GGaHtGT6ZpYxBmBQK1TkIkjt/yuEtLxJ0Oklsrm88FTJrTW51qGfLSLeVP7bMUPeMCQ=="],
+
+ "@ai-sdk/provider": ["@ai-sdk/provider@4.0.24", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-iztFqJTdxNN0ZzqZAAnVPMhRMaL1bfQFa3GIPAffWQ0+dP2FXIkyMD8kHeEI7EpB/C6TR45cqOkrS7YZZ1sATA=="],
+
+ "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.56", "", { "dependencies": { "@ai-sdk/provider": "4.0.24", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-F7/U1YpVXEfSxRHbxScm0PVdr9lN6GaUWHnt8ws2UjuGeuxWHqCkV25dGpHAkpDHkuIXoC+aak/OqSylKSJmHw=="],
+
+ "@ai-sdk/provider-utils-v6": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
+
+ "@ai-sdk/provider-utils-v7": ["@ai-sdk/provider-utils@5.0.13", "", { "dependencies": { "@ai-sdk/provider": "4.0.4", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fScDJMDnTbx32kLDQqp0MvPjvwkgiwvlBxlmIg7XW5PbS91LG6JjH3PQG+34oMFglqfpQA355e24OdGj5PPoDw=="],
+
+ "@ai-sdk/provider-v5": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="],
+
+ "@ai-sdk/provider-v6": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
+
+ "@ai-sdk/provider-v7": ["@ai-sdk/provider@4.0.17", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-VYMBxIQdcHqbIf1j+YZlI9Ati6LZ4wJe0GGd4z4a5H/KxTggjeOiyaVYTnfF7LHZK5jMQ+rofmzz4QPqf++NUw=="],
+
+ "@babel/code-frame": ["@babel/code-frame@8.0.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^8.0.6", "js-tokens": "^10.0.0" } }, "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw=="],
+
+ "@babel/compat-data": ["@babel/compat-data@8.0.5", "", {}, "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw=="],
+
+ "@babel/core": ["@babel/core@8.0.7", "", { "dependencies": { "@babel/code-frame": "^8.0.6", "@babel/generator": "^8.0.6", "@babel/helper-compilation-targets": "^8.0.7", "@babel/helpers": "^8.0.7", "@babel/parser": "^8.0.7", "@babel/template": "^8.0.0", "@babel/traverse": "^8.0.7", "@babel/types": "^8.0.6", "@types/gensync": "^1.0.5", "convert-source-map": "^2.0.0", "empathic": "^2.0.1", "gensync": "^1.0.0-beta.2", "import-meta-resolve": "^4.2.0", "json5": "^2.2.3", "obug": "^3.0.0", "verkit": "^0.5.0" } }, "sha512-ZIsZWBG6Y1oa+8fkQVp0xeCdjCHBkXFCCIWrmg9SvejVvfcVQgKKRmGrBDWfM7rilhvnRoEuaqa5dkK7KB3pHA=="],
+
+ "@babel/generator": ["@babel/generator@8.0.6", "", { "dependencies": { "@babel/parser": "^8.0.6", "@babel/types": "^8.0.6", "@jridgewell/gen-mapping": "0.4.0-beta.0", "@jridgewell/trace-mapping": "^0.3.31", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng=="],
+
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" } }, "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@8.0.7", "", { "dependencies": { "@babel/compat-data": "^8.0.5", "@babel/helper-validator-option": "^8.0.0", "browserslist": "^4.24.0", "flru": "1.0.2", "verkit": "^0.5.0" } }, "sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg=="],
+
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@8.0.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^8.0.0", "@babel/helper-member-expression-to-functions": "^8.0.5", "@babel/helper-optimise-call-expression": "^8.0.0", "@babel/helper-replace-supers": "^8.0.1", "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", "@babel/traverse": "^8.0.7", "verkit": "^0.5.0" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-7ug3KrOhNVPPxFFLTel0/pMzimKzU/vTlNJKVL/dKguCpmNJZjjibyFbWg+Qs8QHmiIZPNuZ2UNnDGdHWGEpOg=="],
+
+ "@babel/helper-globals": ["@babel/helper-globals@8.0.6", "", {}, "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA=="],
+
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@8.0.5", "", { "dependencies": { "@babel/traverse": "^8.0.5", "@babel/types": "^8.0.5" } }, "sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@8.0.0", "", { "dependencies": { "@babel/traverse": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@8.0.6", "", { "dependencies": { "@babel/helper-module-imports": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.6", "@babel/traverse": "^8.0.6" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ=="],
+
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" } }, "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg=="],
+
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@8.0.1", "", { "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g=="],
+
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@8.0.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^8.0.0", "@babel/helper-optimise-call-expression": "^8.0.0", "@babel/traverse": "^8.0.0" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q=="],
+
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@8.0.0", "", { "dependencies": { "@babel/traverse": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.6", "", {}, "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.6", "", {}, "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@8.0.0", "", {}, "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q=="],
+
+ "@babel/helpers": ["@babel/helpers@8.0.7", "", { "dependencies": { "@babel/template": "^8.0.0", "@babel/types": "^8.0.6" } }, "sha512-5dMj5kFwS2AY7Dt4PZp4px3c5ThMbbEa7tZfuHTncCGSFYq8uoA1ufDE1IS/lYpQedE+3+GQ3cPZUiplIu195w=="],
+
+ "@babel/parser": ["@babel/parser@8.0.7", "", { "dependencies": { "@babel/types": "^8.0.6" }, "bin": "./bin/babel-parser.js" }, "sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA=="],
+
+ "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@8.0.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^8.0.1" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg=="],
+
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@8.0.1", "", { "dependencies": { "@babel/helper-module-transforms": "^8.0.1", "@babel/helper-plugin-utils": "^8.0.1" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA=="],
+
+ "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@8.0.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^8.0.0", "@babel/helper-create-class-features-plugin": "^8.0.6", "@babel/helper-plugin-utils": "^8.0.1", "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", "@babel/plugin-syntax-typescript": "^8.0.3" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw=="],
+
+ "@babel/preset-typescript": ["@babel/preset-typescript@8.0.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^8.0.1", "@babel/helper-validator-option": "^8.0.0", "@babel/plugin-transform-modules-commonjs": "^8.0.1", "@babel/plugin-transform-typescript": "^8.0.6" }, "peerDependencies": { "@babel/core": "^8.0.0" } }, "sha512-sUFLFphAG6YNjCnJpSKpyoNxkfyeM2KNuUjJJN0R6dZusMcK2V3Fg11MVHPFvb37AZeDNocc+xM748vdh9z82Q=="],
+
+ "@babel/template": ["@babel/template@8.0.0", "", { "dependencies": { "@babel/code-frame": "^8.0.0", "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0" } }, "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ=="],
+
+ "@babel/traverse": ["@babel/traverse@8.0.7", "", { "dependencies": { "@babel/code-frame": "^8.0.6", "@babel/generator": "^8.0.6", "@babel/helper-globals": "^8.0.6", "@babel/parser": "^8.0.7", "@babel/template": "^8.0.0", "@babel/types": "^8.0.6", "obug": "^3.0.0" } }, "sha512-bVV0brinsTWgc6ptSIoQo3GbITtl0VZAqEUJbHHcyfDPEu+q/fEvlYEa+6H85HoK8IdksKBFtKJbJFZ6Kff+zg=="],
+
+ "@babel/types": ["@babel/types@8.0.6", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.6", "@babel/helper-validator-identifier": "^8.0.6" } }, "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A=="],
+
+ "@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="],
+
+ "@clack/prompts": ["@clack/prompts@1.8.1", "", { "dependencies": { "@clack/core": "1.5.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
+
+ "@expo/devcert": ["@expo/devcert@1.2.2", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-wz1YedXwdNbYqk/PRr34vixnuw1+l6uSwmPXUj649cot5iYNccb2zIOnK7Nonb1Pfd/dxAY6CvU6KM3uSdxC7A=="],
+
+ "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
+
+ "@hono/node-server": ["@hono/node-server@1.19.17", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ=="],
+
+ "@hono/node-ws": ["@hono/node-ws@1.3.1", "", { "dependencies": { "ws": "^8.17.0" }, "peerDependencies": { "@hono/node-server": "^1.19.11", "hono": "^4.6.0" } }, "sha512-vo/MwCnpJAVHBkGzWjCJ28wF45fYHAfbPZcH2rodZODHtch2GHA94KtMfusmVycTUtsLAsaNsHhtY6P8X3RQsA=="],
+
+ "@hono/standard-validator": ["@hono/standard-validator@0.4.0", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "hono": ">=4.11.2" } }, "sha512-MxOm1asDh9j7c1D0KiWwppSbFgAQxTRO4YEhjMrJn3lF1BIcXenPdgjSGKDRfQmZdaTaMA8slQLETgVOKyJxFw=="],
+
+ "@isaacs/ttlcache": ["@isaacs/ttlcache@2.1.5", "", {}, "sha512-VwGZqqjAWPICTmxUZnbpEfO60LhPWzquik+bmyXGY7pYRn6diEvCI5i6Ca+J6o2y4vS73HrpuMTo2dOvUevH8w=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.4.0-beta.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@lukeed/csprng": ["@lukeed/csprng@1.1.0", "", {}, "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="],
+
+ "@lukeed/uuid": ["@lukeed/uuid@2.0.1", "", { "dependencies": { "@lukeed/csprng": "^1.1.0" } }, "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w=="],
+
+ "@mastra/core": ["@mastra/core@1.75.0", "", { "dependencies": { "@a2a-js/sdk-v0_3": "npm:@a2a-js/sdk@~0.3.14", "@a2a-js/sdk-v1": "npm:@a2a-js/sdk@~1.0.1", "@ai-sdk/provider-utils-v6": "npm:@ai-sdk/provider-utils@4.0.40", "@ai-sdk/provider-utils-v7": "npm:@ai-sdk/provider-utils@5.0.13", "@ai-sdk/provider-v5": "npm:@ai-sdk/provider@2.0.3", "@ai-sdk/provider-v6": "npm:@ai-sdk/provider@3.0.14", "@ai-sdk/provider-v7": "npm:@ai-sdk/provider@4.0.17", "@isaacs/ttlcache": "^2.1.5", "@lukeed/uuid": "^2.0.1", "@mastra/schema-compat": "1.3.13", "@standard-schema/spec": "^1.1.0", "chat": "^4.37.0", "croner": "^10.0.1", "dotenv": "^17.3.1", "execa": "^9.6.1", "fastq": "^1.20.1", "gray-matter": "^4.0.3", "hono": "^4.13.7", "hono-openapi": "^1.3.1", "ignore": "^7.0.5", "jpeg-js": "^0.4.4", "json-schema": "^0.4.0", "lru-cache": "^11.2.7", "p-map": "^7.0.4", "p-retry": "^7.1.1", "picomatch": "^4.0.3", "posthog-node": "^5.46.1", "re2js": "^2.8.6", "ws": "^8.21.3", "xxhash-wasm": "^1.1.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1yw89tyha4HU1eO34IC5sYdSCbXuw8WCVf4+pqmNyRmoHp0ZcPzyberdsbq9O8CTHuxKZbf7cgsI2QNI2L3SRA=="],
+
+ "@mastra/deployer": ["@mastra/deployer@1.75.0", "", { "dependencies": { "@babel/core": "^8.0.1", "@babel/preset-typescript": "^8.0.1", "@babel/traverse": "^8.0.4", "@hono/node-ws": "^1.3.0", "@mastra/server": "1.75.0", "@optimize-lodash/rollup-plugin": "^5.1.0", "@rollup/plugin-alias": "6.0.0", "@rollup/plugin-commonjs": "29.0.2", "@rollup/plugin-esm-shim": "0.1.8", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "16.0.3", "@rollup/plugin-virtual": "3.0.2", "@sindresorhus/slugify": "^2.2.1", "empathic": "^2.0.0", "esbuild": "^0.28.0", "find-up-simple": "^1.0.1", "find-workspaces": "^0.3.1", "fs-extra": "^11.3.5", "gray-matter": "^4.0.3", "hono": "^4.13.7", "local-pkg": "^1.1.2", "resolve.exports": "^2.0.3", "rollup": "^4.61.1", "rollup-plugin-esbuild": "^6.2.1", "strip-json-comments": "^5.0.3", "tinyglobby": "^0.2.17", "typescript-paths": "^1.5.2", "ws": "^8.21.3", "yaml": "^2.9.0" }, "peerDependencies": { "@mastra/core": ">=1.58.0-0 <2.0.0-0" } }, "sha512-6rJ11ZrYcDnt+WxkJMuZyAAUjrqrK/gDdv30+1tQBvtYcgHDfeuIW6IKbYfowU3C/EhqQtLPDAPlm4OlPQKKhQ=="],
+
+ "@mastra/loggers": ["@mastra/loggers@1.3.5", "", { "dependencies": { "pino": "^10.3.1", "pino-pretty": "^13.1.3" }, "peerDependencies": { "@mastra/core": ">=1.0.0-0 <2.0.0-0" } }, "sha512-wGI2xle5VWAvRLXAbT2kzOlUovy5rRD89l0HoPNN12a2KU7QfLCX686KqCD0SrzVAzHENYHrgNm6Ek1lSSCkEg=="],
+
+ "@mastra/schema-compat": ["@mastra/schema-compat@1.3.13", "", { "dependencies": { "json-schema-to-zod": "^2.7.0", "zod-from-json-schema": "^0.5.2" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-L3AkWcujcCC9qKYiGOxjRN0NrNMIxLAwn/ayjNlD3zhFSUBDI45LmlFxAYHLeAz315uogKb+9tZA/TjHnjb95w=="],
+
+ "@mastra/server": ["@mastra/server@1.75.0", "", { "dependencies": { "hono": "^4.13.7" }, "peerDependencies": { "@mastra/core": ">=1.58.0-0 <2.0.0-0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-yFhGn8vfGjXCfVkJJOhh9psTk5PrI7nVyEjeFao0L+dOXD5smsZ+to5WqL7UpasIm02dBLQxmJ5IqNTuoZacdw=="],
+
+ "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+
+ "@optimize-lodash/rollup-plugin": ["@optimize-lodash/rollup-plugin@5.1.0", "", { "dependencies": { "@optimize-lodash/transform": "3.0.6", "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": ">= 4.x" } }, "sha512-dBQYGH8+n4Z/877e61PJteVZEc+U1wfRF6IhKqW0cABRIsqMxpWynyov6M6Sd4IUjru0dnh0EG55X86JO6WakQ=="],
+
+ "@optimize-lodash/transform": ["@optimize-lodash/transform@3.0.6", "", { "dependencies": { "estree-walker": "^2.0.2", "magic-string": "~0.30.11" } }, "sha512-9+qMSaDpahC0+vX2ChM46/ls6a5Ankqs6RTLrHSaFpm7o1mFanP82e+jm9/0o5D660ueK8dWJGPCXQrBxBNNWA=="],
+
+ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
+
+ "@posthog/core": ["@posthog/core@1.57.2", "", { "dependencies": { "@posthog/types": "^1.415.1" } }, "sha512-dfxeSjUvR+j4M7odvyf/KVdcKTZZ87u/l9swJtDt9vFkI2km3RhEveOkIHtFcZEQ1cllx7YaKJfdw6c8zfIEvg=="],
+
+ "@posthog/types": ["@posthog/types@1.415.1", "", {}, "sha512-7Qs2wcqipl8KcFpIkIKE1Wcj1djX4dEB3cfq907HrNpKK7/xKh3U4N4bkOD6h9+8j3SxWWrj9JNpE8CZXsE35w=="],
+
+ "@rollup/plugin-alias": ["@rollup/plugin-alias@6.0.0", "", { "peerDependencies": { "rollup": ">=4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g=="],
+
+ "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.2", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg=="],
+
+ "@rollup/plugin-esm-shim": ["@rollup/plugin-esm-shim@0.1.8", "", { "dependencies": { "magic-string": "^0.30.3", "mlly": "^1.7.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-xEU0b/BShgDDSPjidhJd4R74J9xZ9jLVtFWNGtsUXyEsdwwwB1a3XOAwwGaNIyUHD6EhxPO21JMfUmJWoMn7SA=="],
+
+ "@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
+
+ "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
+
+ "@rollup/plugin-virtual": ["@rollup/plugin-virtual@3.0.2", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A=="],
+
+ "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.64.2", "", { "os": "android", "cpu": "arm" }, "sha512-gjNDUwMswogkw3fNqV00u54uZlgmInuw5EU/1TeqQ6K0bEagoGvqjPdKzy112lKyhe7PkGudZ/kSQHsc9qroIg=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.64.2", "", { "os": "android", "cpu": "arm64" }, "sha512-YkAW8q7p5SCLSaGWaUkmsRnBEb3MGA1pB5nI/8k0lAK5fZsQKRBqkdR1h4UU9p8u3c7ZGdUF2Zcx7mK+E/pUKg=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.64.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hNmuIHv+LnrP/+q/44vsmIMWfoh7X2YXOp5FLtjGuvqN52VdtFpOpnshTc1k28tWv1wE/0D5gLjsozBUowEGbQ=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.64.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-B0t5CoxgF2KL7R5FHAsyN8yVEQCSIkh4XVs8MCDjuBLNjEa8l3zd5tVKaFMv53LfUtHs9p2rnqeavPTOunV+Ww=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.64.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QQdYnfwrPmc1DoPlddjFvQPzUHFbStKXsHrG6EBYsWHhBVUCjsCRdcfGGXGAvhDpDtTzKK4vy1UrGFJ45rQaiw=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.64.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1xRxtGWif6BB58VSfMavi0Yfs2M4x6CtRp4o7ScY4W7AGlgvf6GSPKdDNdw7TX3tMiQwZY5W/mvyrOJTq/7AKA=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.64.2", "", { "os": "linux", "cpu": "arm" }, "sha512-CXFLEt384BUwwOLR9DURWNHl1bQOVLd1ibOee+yKBJm/fZtvrSAOZ0JbxjAxszVMIovYMwFMS+oP+Q1aPcx8Eg=="],
+
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.64.2", "", { "os": "linux", "cpu": "arm" }, "sha512-T1dorY0WHm5mMNpMHFl5rYRZ02gLSZcr6J45r66tYb3S3R3DyIRGiGWVdLtF+zOQjratNJaObl9jYumueRiL1A=="],
+
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.64.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-ia6bfaCIc0ldp6bvs9oEkFrJVJKNUMoiDF0+LBZsQAZg+vE0znXnzGqH+ew/FYswE30cruAWPTO3dPw7keaVkQ=="],
+
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.64.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-mB/gwfBfXtT0EBpbSssqKCqMFuX8b21Zy2TGoIhehSJEssZ5ksybxymwp1BkEM5Nczh/AAxpmh5q+MkhtundGg=="],
+
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.64.2", "", { "os": "linux", "cpu": "none" }, "sha512-VPd0a+XlHoNRI9jIPRHww2UKhDkD6XjbtcKYXZijAnhJS1jLLbiNtVl6a5LyEfNN+eyiyWcJOZXPzBo69oTsew=="],
+
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.64.2", "", { "os": "linux", "cpu": "none" }, "sha512-10N1I6oBpERqzGtHKcCLrEpHlbXSBbMK9TURHvqgWbIFZuU3oJROxJ0H2nHfq96TVI0XJHQ9qzolJMFd2tz0LA=="],
+
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.64.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-dhJrLVZ3wNNhwCmy9/VTg6Pq6J8a8eZ0n15dVRlbSWcDb+8V/1mDfbnvis+vt8W6Pbxzv6gUbTEl4piOT3bbUQ=="],
+
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.64.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tPd2QuwxkTxgju5eARyb3RdgBWWi/O9dn/NFH0VkvGbLzoQeXrH6qMABtW8IODb5lUeBzB3sy2U7qmI4MtQ72g=="],
+
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.64.2", "", { "os": "linux", "cpu": "none" }, "sha512-njtDdPvSQWI9CDhIPPxsiEMeHayCfu7wwcbSQ7c2kYPhGVviL+WGVhq6Jfj/b+VuhSTkFuQ0z4ZFbFjkWwBFmg=="],
+
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.64.2", "", { "os": "linux", "cpu": "none" }, "sha512-EbOBo3dqCj9ecEV9Z7zvulB6AieMP2ztnKQYGVOSN0pzIyOu+3JH2YJZnLmbPeVvfn6yP6Qi7NbBM27EzGbj2w=="],
+
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.64.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-8wLt2Q5n4qmfiE2Gk8V6KNLSf+oJuBX7Kdw5joFDtnZIZvirS0hLzBc79OiJjV71oYDmBUZLNy6pDgvylo4qiQ=="],
+
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.64.2", "", { "os": "linux", "cpu": "x64" }, "sha512-aIChbkhC4d33bxkVP0LSi2/2lUSGRXE6k4Qa0G7tgpgTOK7kxHTixmx5qC3SKUJ6yUMznHBtQoWaG4QMqLUnhw=="],
+
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.64.2", "", { "os": "linux", "cpu": "x64" }, "sha512-gQ+TkVZRfjZVcl/UOAxxfWw+sHjzIXoieNwxEyJlD3tRBmBhR8ZDq11w+QEgQDcpslEB+fhN/OEpaTsDoIIHhQ=="],
+
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.64.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-sUaJpMKS2JBCvUk8fYimG7aMq2jHVT4kw/uVHxPolKLrMmkKy0KXirmZyZehdNpL7PffqAlPAWUELQcTiD8ChA=="],
+
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.64.2", "", { "os": "none", "cpu": "arm64" }, "sha512-j1RdJWb0CrlSuwt12OPjLucPlwfLtA/KMOKVvh5rXSJEtl/WDoIGR3CSykK/jLGTo9RqNfW5Ub6kY5Z5ccYl9w=="],
+
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.64.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-i3r04aHCIY3eggTBLUp/4IxiBjDpAgQaK89Ue2nMJ2MKuIXX5JfoeZjHw+kPjGGNVDlhi8FT8xzuGMJZe3aehg=="],
+
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.64.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-kCQ15h/uwNMGdN8gjXBZGf+aGE+ODzHfbi90/cAm4+Q89WoMiLODNoj8HoGZmYx87HPxfu5CBNPsZ+ZG4uIAoA=="],
+
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.64.2", "", { "os": "win32", "cpu": "x64" }, "sha512-yYAuySSs+0X2gSMJPw/Q4jHsHZHyMq1dpAE+XnN790Jg1WuReUAD/+j+e4vuDvS4PXJ4PA4NCzTR4OYtDf18/g=="],
+
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.64.2", "", { "os": "win32", "cpu": "x64" }, "sha512-JY1rQdWOJrKutORqKO1uzrHXV86jFiQzfLFKx2s8S1CjLXL9fC5LOIvk0q7Z7PXJvsByt+sdoE1Q2a9gBr4gLQ=="],
+
+ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
+
+ "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
+
+ "@sindresorhus/slugify": ["@sindresorhus/slugify@2.2.1", "", { "dependencies": { "@sindresorhus/transliterate": "^1.0.0", "escape-string-regexp": "^5.0.0" } }, "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw=="],
+
+ "@sindresorhus/transliterate": ["@sindresorhus/transliterate@1.6.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0" } }, "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ=="],
+
+ "@standard-community/standard-json": ["@standard-community/standard-json@0.3.6", "", { "dependencies": { "quansync": "^0.2.11" }, "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.20.0", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.4.2", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-tuXKz1Ps4al0W4xhfwii7v4lskKtOv3QE7mcouRVThrBdOR6JzY4Vea3nsxcEnte84bOJvMvXGmOMWE3IBbHyA=="],
+
+ "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.10", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.20.0", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.4.2", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-whaCN9eu5zv5gu6Kyeh5am/S3AyS1nqo57/wAxs/KPS7+94UhBUZmkIetmXzm22qIV0zMrHt3y+2VfjkbkYYTA=="],
+
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
+
+ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
+
+ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
+
+ "@types/gensync": ["@types/gensync@1.0.5", "", {}, "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg=="],
+
+ "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="],
+
+ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+
+ "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+
+ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+
+ "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
+
+ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+
+ "@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "acorn": ["acorn@8.19.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw=="],
+
+ "archiver": ["archiver@8.0.0", "", { "dependencies": { "async": "^3.2.4", "buffer-crc32": "^1.0.0", "is-stream": "^4.0.0", "lazystream": "^1.0.0", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^3.0.0", "tar-stream": "^3.0.0", "zip-stream": "^7.0.2" } }, "sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g=="],
+
+ "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+
+ "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
+
+ "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
+
+ "b4a": ["b4a@1.9.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ=="],
+
+ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "bare-events": ["bare-events@2.9.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA=="],
+
+ "bare-fs": ["bare-fs@4.8.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-+ZI68KHMUvosXfKbg/UOHK0tbCdRnegbvPEdEcZ3Nd6TetieQsJPRXBRXPdLyy8+3VSEbPXtsumTpEtt78xv9w=="],
+
+ "bare-path": ["bare-path@3.1.2", "", {}, "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw=="],
+
+ "bare-stream": ["bare-stream@2.13.4", "", { "dependencies": { "b4a": "^1.8.1", "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA=="],
+
+ "bare-url": ["bare-url@2.5.4", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.11.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ=="],
+
+ "brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="],
+
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+
+ "browserslist": ["browserslist@4.29.3", "", { "dependencies": { "baseline-browser-mapping": "^2.11.26", "caniuse-lite": "^1.0.30001813", "electron-to-chromium": "^1.5.439", "node-releases": "^2.0.57", "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA=="],
+
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
+ "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
+
+ "bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001815", "", {}, "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q=="],
+
+ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+
+ "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
+
+ "chat": ["chat@4.41.1", "", { "dependencies": { "@workflow/serde": "4.1.0-beta.2", "mdast-util-to-string": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "remend": "^1.2.1", "unified": "^11.0.5" }, "peerDependencies": { "ai": "^6.0.182 || ^7.0.0", "workflow": "^5.0.0-beta.35", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai", "workflow", "zod"] }, "sha512-+/LDcLoYLihf/9Kp5ymj2ylvm6q5vySQTMn0ZoeU0UWDfNVmCNc6OiADW8hwnejOLfevUs8lEpgotG9rmIxZvg=="],
+
+ "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
+
+ "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
+
+ "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
+
+ "compress-commons": ["compress-commons@7.0.1", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^7.0.1", "is-stream": "^4.0.0", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+ "confbox": ["confbox@0.3.1", "", {}, "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA=="],
+
+ "content-disposition": ["content-disposition@0.5.2", "", {}, "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
+
+ "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
+
+ "crc32-stream": ["crc32-stream@7.0.1", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g=="],
+
+ "croner": ["croner@10.0.1", "", {}, "sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
+
+ "debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+
+ "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
+
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+
+ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+
+ "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.451", "", {}, "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg=="],
+
+ "empathic": ["empathic@2.1.0", "", {}, "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw=="],
+
+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
+
+ "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
+
+ "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+
+ "eventsource-parser": ["eventsource-parser@3.1.1", "", {}, "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ=="],
+
+ "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
+
+ "exsolve": ["exsolve@1.1.3", "", {}, "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA=="],
+
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+
+ "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
+
+ "fast-copy": ["fast-copy@4.1.2", "", {}, "sha512-DUHggaYcouhy5I1LacDJ+JDSqGlumVJP2EAi5Nwt1pWPeLpSvIjkrY+x4fsoI1/+GlTyq+DM9zdgWCFSfqNODA=="],
+
+ "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
+
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+
+ "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
+
+ "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
+
+ "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
+
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
+
+ "fastq": ["fastq@1.20.3", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
+
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
+ "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="],
+
+ "find-workspaces": ["find-workspaces@0.3.1", "", { "dependencies": { "fast-glob": "^3.3.2", "pkg-types": "^1.0.3", "yaml": "^2.3.4" } }, "sha512-UDkGILGJSA1LN5Aa7McxCid4sqW3/e+UYsVwyxki3dDT0F8+ym0rAfnCkEfkL0rO7M+8/mvkim4t/s3IPHmg+w=="],
+
+ "flru": ["flru@1.0.2", "", {}, "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog=="],
+
+ "fs-extra": ["fs-extra@11.4.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-port": ["get-port@7.2.0", "", {}, "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg=="],
+
+ "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
+
+ "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="],
+
+ "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
+
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
+
+ "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
+
+ "hono": ["hono@4.13.13", "", {}, "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA=="],
+
+ "hono-openapi": ["hono-openapi@1.3.5", "", { "dependencies": { "@hono/standard-validator": "^0.4.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "openapi-types": "^12.1.3" }, "peerDependencies": { "hono": "^4.11.2" } }, "sha512-t1YpXno9oXa6400xi9rX6i1jKB0496VvikcVKpN/UY3s7yDDQqeCgnKlDr3rfkqxGvykq3+bgX/64eCm7UnUMA=="],
+
+ "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "ignore": ["ignore@7.0.12", "", {}, "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg=="],
+
+ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "is-core-module": ["is-core-module@2.17.0", "", { "dependencies": { "hasown": "^2.0.4" } }, "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA=="],
+
+ "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
+
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+ "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
+
+ "is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
+
+ "is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
+
+ "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
+
+ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
+
+ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "jose": ["jose@6.2.12", "", {}, "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="],
+
+ "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
+
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
+
+ "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="],
+
+ "js-yaml": ["js-yaml@3.15.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
+
+ "json-schema-to-zod": ["json-schema-to-zod@2.8.1", "", { "bin": { "json-schema-to-zod": "dist/cjs/cli.js" } }, "sha512-fRr1mHgZ7hboLKBUdR428gd9dIHUFGivUqOeiDcSmyXkNZCtB1uGaZLvsjZ4GaN5pwBIs+TGIOf6s+Rp5/R/zA=="],
+
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+ "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
+
+ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
+
+ "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
+
+ "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="],
+
+ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
+
+ "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="],
+
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
+
+ "mastra": ["mastra@1.33.0", "", { "dependencies": { "@babel/parser": "^8.0.4", "@babel/types": "^8.0.4", "@clack/prompts": "^1.7.0", "@expo/devcert": "^1.2.1", "@mastra/deployer": "^1.75.0", "@mastra/loggers": "^1.3.5", "archiver": "^8.0.0", "commander": "^14.0.3", "dotenv": "^17.3.1", "execa": "^9.6.1", "fs-extra": "^11.3.5", "get-port": "^7.1.0", "local-pkg": "^1.1.2", "openapi-fetch": "^0.17.0", "picocolors": "^1.1.1", "posthog-node": "^5.46.1", "semver": "^7.7.4", "serve-handler": "^6.1.7", "strip-json-comments": "^5.0.3", "tinyglobby": "^0.2.17", "yocto-spinner": "^1.1.0" }, "peerDependencies": { "@mastra/core": ">=1.58.0-0 <2.0.0-0" }, "bin": { "mastra": "./dist/index.js" } }, "sha512-m1R3Y8vOfVLclqMCM0VzPsgG7EvYWpLNr9TmL6ZEN4eIKXvHX2tyvQUzKzalgaLD2MffhIl7NYtitT2AGDMIww=="],
+
+ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw=="],
+
+ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A=="],
+
+ "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
+
+ "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
+
+ "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
+
+ "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw=="],
+
+ "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
+
+ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
+
+ "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
+
+ "mdast-util-to-markdown": ["mdast-util-to-markdown@2.2.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-character": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA=="],
+
+ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+
+ "micromark": ["micromark@4.0.3", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-edit-map": "^1.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw=="],
+
+ "micromark-core-commonmark": ["micromark-core-commonmark@2.0.4", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.1.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-edit-map": "^1.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.3" } }, "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w=="],
+
+ "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
+
+ "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
+
+ "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
+
+ "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
+
+ "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA=="],
+
+ "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
+
+ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
+
+ "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
+
+ "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
+
+ "micromark-factory-space": ["micromark-factory-space@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q=="],
+
+ "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
+
+ "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
+
+ "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
+
+ "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
+
+ "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
+
+ "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
+
+ "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
+
+ "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
+
+ "micromark-util-edit-map": ["micromark-util-edit-map@1.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q=="],
+
+ "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
+
+ "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
+
+ "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
+
+ "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
+
+ "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
+
+ "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
+
+ "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
+
+ "micromark-util-types": ["micromark-util-types@2.0.3", "", {}, "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "mime-db": ["mime-db@1.33.0", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="],
+
+ "mime-types": ["mime-types@2.1.18", "", { "dependencies": { "mime-db": "~1.33.0" } }, "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ=="],
+
+ "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "node-releases": ["node-releases@2.0.57", "", {}, "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw=="],
+
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
+ "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
+
+ "obug": ["obug@3.0.0", "", {}, "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw=="],
+
+ "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
+
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+ "openapi-fetch": ["openapi-fetch@0.17.0", "", { "dependencies": { "openapi-typescript-helpers": "^0.1.0" } }, "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig=="],
+
+ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
+
+ "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="],
+
+ "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="],
+
+ "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="],
+
+ "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
+
+ "path-is-inside": ["path-is-inside@1.0.2", "", {}, "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "path-to-regexp": ["path-to-regexp@3.3.0", "", {}, "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
+
+ "pino": ["pino@10.4.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^1.0.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-bk1ZMTwG/Vymx+zPoa28MhNzucFvXJWp3csrSuxuHPSMeUettaCoQ5Jvg07mtC4JGNpcfTwz41oVCnNgBK9+vA=="],
+
+ "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
+
+ "pino-pretty": ["pino-pretty@13.2.0", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.2", "pino-abstract-transport": "^3.0.0", "pump": "^3.0.3", "secure-json-parse": "^4.1.0", "sonic-boom": "^5.0.0", "strip-json-comments": "^5.0.3" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-jjghD1ML+Di/WxjJDz6j6Y4FT7hIXOlb8h27AyJ+ohtAXJXH4PS/xkv7si+RCCIW59HMgZsMKTDgzdr0RhuRrg=="],
+
+ "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
+
+ "pkg-types": ["pkg-types@2.3.3", "", { "dependencies": { "confbox": "^0.3.1", "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w=="],
+
+ "posthog-node": ["posthog-node@5.55.0", "", { "dependencies": { "@posthog/core": "^1.55.2" }, "peerDependencies": { "rxjs": "^7.0.0" }, "optionalPeers": ["rxjs"] }, "sha512-8dFtlDTqs0CKGZ8t4mUnk4NdShAgzru3B9D0MfM/NTBzlRyAjctivzWINLtMLRQxYjvgBohxy7RTwiQf4mPycQ=="],
+
+ "pretty-ms": ["pretty-ms@9.3.1", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA=="],
+
+ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
+
+ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
+
+ "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="],
+
+ "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
+
+ "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
+
+ "range-parser": ["range-parser@1.2.0", "", {}, "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A=="],
+
+ "re2js": ["re2js@2.8.6", "", {}, "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg=="],
+
+ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
+
+ "readdir-glob": ["readdir-glob@3.0.0", "", { "dependencies": { "minimatch": "^10.2.2" } }, "sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw=="],
+
+ "real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
+
+ "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
+
+ "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
+
+ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
+
+ "remend": ["remend@1.4.0", "", {}, "sha512-mHiYhhwphs1IgIZmkToKLwJn0U99IjPXuutkA2acrNcG0vlEq2G4TGnd2eT0saIHwh2KjaCpvleHcsd5SAz96A=="],
+
+ "resolve": ["resolve@1.22.13", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.17.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA=="],
+
+ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
+ "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
+
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+ "rollup": ["rollup@4.64.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.64.2", "@rollup/rollup-android-arm64": "4.64.2", "@rollup/rollup-darwin-arm64": "4.64.2", "@rollup/rollup-darwin-x64": "4.64.2", "@rollup/rollup-freebsd-arm64": "4.64.2", "@rollup/rollup-freebsd-x64": "4.64.2", "@rollup/rollup-linux-arm-gnueabihf": "4.64.2", "@rollup/rollup-linux-arm-musleabihf": "4.64.2", "@rollup/rollup-linux-arm64-gnu": "4.64.2", "@rollup/rollup-linux-arm64-musl": "4.64.2", "@rollup/rollup-linux-loong64-gnu": "4.64.2", "@rollup/rollup-linux-loong64-musl": "4.64.2", "@rollup/rollup-linux-ppc64-gnu": "4.64.2", "@rollup/rollup-linux-ppc64-musl": "4.64.2", "@rollup/rollup-linux-riscv64-gnu": "4.64.2", "@rollup/rollup-linux-riscv64-musl": "4.64.2", "@rollup/rollup-linux-s390x-gnu": "4.64.2", "@rollup/rollup-linux-x64-gnu": "4.64.2", "@rollup/rollup-linux-x64-musl": "4.64.2", "@rollup/rollup-openbsd-x64": "4.64.2", "@rollup/rollup-openharmony-arm64": "4.64.2", "@rollup/rollup-win32-arm64-msvc": "4.64.2", "@rollup/rollup-win32-ia32-msvc": "4.64.2", "@rollup/rollup-win32-x64-gnu": "4.64.2", "@rollup/rollup-win32-x64-msvc": "4.64.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RxXm0XVVV1hFI3dYe12ruaOgsYQ725lUcUWeMSQqkF9tTxVErY+fy2Ets6vWhItL9MZMSNZGbvZFc7rH/VHp2Q=="],
+
+ "rollup-plugin-esbuild": ["rollup-plugin-esbuild@6.2.1", "", { "dependencies": { "debug": "^4.4.0", "es-module-lexer": "^1.6.0", "get-tsconfig": "^4.10.0", "unplugin-utils": "^0.2.4" }, "peerDependencies": { "esbuild": ">=0.18.0", "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" } }, "sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA=="],
+
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+ "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
+
+ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
+
+ "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
+
+ "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
+
+ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "serve-handler": ["serve-handler@6.1.7", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
+ "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
+
+ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
+
+ "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
+ "streamx": ["streamx@2.28.1", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
+
+ "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
+
+ "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "tar-stream": ["tar-stream@3.2.2", "", { "dependencies": { "b4a": "^1.9.0", "bare-fs": "^4.8.2", "fast-fifo": "^1.3.2", "streamx": "^2.28.1" } }, "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg=="],
+
+ "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="],
+
+ "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="],
+
+ "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="],
+
+ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
+
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
+ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+
+ "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
+
+ "typescript-paths": ["typescript-paths@1.5.2", "", { "peerDependencies": { "typescript": "^4.7.2 || ^5 || ^6" } }, "sha512-s5iiRIWOSw80dBgPACm0asPQZHHQcbPz69f26eRq01dStusuD11idZ0NDcAbkj6WuUGcRwJediPOsrArIS92eg=="],
+
+ "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
+
+ "undici": ["undici@7.30.0", "", {}, "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ=="],
+
+ "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
+
+ "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
+
+ "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
+
+ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+
+ "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
+
+ "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+
+ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
+
+ "unplugin-utils": ["unplugin-utils@0.2.5", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.3.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
+
+ "verkit": ["verkit@0.5.0", "", {}, "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g=="],
+
+ "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
+
+ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+ "ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="],
+
+ "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
+
+ "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="],
+
+ "yocto-spinner": ["yocto-spinner@1.2.2", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng=="],
+
+ "yoctocolors": ["yoctocolors@2.2.0", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="],
+
+ "zip-stream": ["zip-stream@7.0.5", "", { "dependencies": { "compress-commons": "^7.0.0", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w=="],
+
+ "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
+
+ "zod-from-json-schema": ["zod-from-json-schema@0.5.6", "", { "dependencies": { "zod": "^4.0.17" } }, "sha512-U33AJ7ZWS6y9XNSzMWcdy8hRAvZmWhTtpYJu0SXPT5AArbc9nq2ur7Magzmn5RF9KBV4b3FP0nCpmqqlfXlR9w=="],
+
+ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+
+ "@ai-sdk/provider-utils-v6/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
+
+ "@ai-sdk/provider-utils-v7/@ai-sdk/provider": ["@ai-sdk/provider@4.0.4", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-tbHKNLirllUNF3ZlkCsXnwab2ZV1Sl4b1H/Cp9ruCce15IBmskE8Gwkk0yo9xDWY+jho2of7lVXtwSsyrq7cwQ=="],
+
+ "chat/@workflow/serde": ["@workflow/serde@4.1.0-beta.2", "", {}, "sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww=="],
+
+ "find-workspaces/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+
+ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+
+ "micromark/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+
+ "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
+
+ "pino-pretty/sonic-boom": ["sonic-boom@5.0.2", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-/JEruzo6yhNVhsLblTRchgTQHIt7SsdJP0YyrfWaNKjIOQDfrdZv9gwYraYCAsmKV39/LAdV31FLL5NCDpoL/A=="],
+
+ "readdir-glob/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
+
+ "rollup-plugin-esbuild/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "find-workspaces/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+
+ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
+
+ "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+
+ "readdir-glob/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="],
+
+ "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ }
+}
diff --git a/app/src/components/gallery/activity.tsx b/app/src/components/gallery/activity.tsx
index e72d56731..1b10598e7 100644
--- a/app/src/components/gallery/activity.tsx
+++ b/app/src/components/gallery/activity.tsx
@@ -5,6 +5,7 @@ import { callComponentFunction } from "@/lib/components/queries";
import { useActiveBotId } from "@/lib/copilot/active-bot";
import { useConversation } from "@/lib/copilot/conversation";
import type { GalleryComponent } from "@/lib/copilot/gallery-registry";
+import { keyedLookup } from "@/lib/lookup";
import { GalleryFrame } from "./frame";
import { seriesColour } from "./palette";
@@ -33,11 +34,19 @@ export const ActivityReportProps = z.object({
type ActivityArgs = z.infer
+ {enableError}
+
+ {/*
+ * The server's own sentence, as the enable failure above already does. A keyless
+ * deployment is refused with one naming the key's environment variable, and that name is
+ * the single thing an operator who reached this URL needs to read; the derived line
+ * stays as the fallback for a failure that arrived carrying no message of its own.
+ */}
+ {apps.error.message || directoryReadFailure(provider)}
+
+ {warning}
+
+ {breadth}
+
+ ) : (
+
+ {landedActionNotice(landedAction)} +
+ ) : null} + {/* One decision, so no heading: it would only repeat the row's own title. */}That account could not be connected. Nothing was saved — try again.
@@ -271,6 +407,16 @@ function RouteComponent() { ) ) : null} + {/* + * The brokered per-action verifier's own channel. It lands here rather than on an app's page + * because three of its refusals have no app to name, and a notice that sometimes appears one + * level in and sometimes here would be a notice people learn to look for in two places. + */} + {arrival.authorizationNotice ? ( ++ {arrival.authorizationNotice} +
+ ) : null} {/* * BOTH READS DECIDE THIS, AND ONLY ONE OF THEM USED TO. The waits were already paired here; * the errors were not — this branch tested `plugins.error` alone, so a `/api/plugins` that diff --git a/app/src/routes/sign.tsx b/app/src/routes/sign.tsx index 1d03a7afe..b83b2ec06 100644 --- a/app/src/routes/sign.tsx +++ b/app/src/routes/sign.tsx @@ -12,7 +12,9 @@ import { signInWith, signInWithEmailDomain, } from "@/lib/auth/client"; +import { resumeUrlFor, verifierPathFor } from "@/lib/auth/resume"; import { appConfig } from "@/lib/generated/application-config"; +import { searchTextEntry } from "@/lib/search-text"; import { type AuthProviderId, authProvidersQueryOptions, @@ -26,12 +28,43 @@ const ENTRANCE_STAGGER_SECONDS = 0.08; const ENTRANCE_OFFSET = "translateY(12px)"; export const Route = createFileRoute("/sign")({ - beforeLoad: async ({ context }) => { + /* + * `?flow_id=` is the handle a brokered authorization arrived with, sent here when its session had + * lapsed. It is a value to carry back, never a place to go: see `resumeUrlFor`. + * + * READ AS TEXT WHATEVER THE ROUTER PARSED IT INTO, which is what `searchText` is for: a handle + * made of digits arrives here as a number and a JSON-shaped one as an object, so a `typeof` + * guard would drop the correlation for whichever handles the vendor's generator happens to emit + * — working in testing and losing the return leg's binding in production. + * + * The key is omitted rather than set to undefined, for the reason the connected-accounts screen + * gives for `?connected=`: present-but-undefined makes `search` a required prop on every Link to + * this route, which is a lot of ripple for a parameter only the verifier sets. + */ + validateSearch: (search: Recordthe plugins index
, + }); + const wired = ( + ArcadeRoute as unknown as { + update: (options: unknown) => typeof ArcadeRoute; + } + ).update({ + id: "/plugins/arcade", + path: "/plugins/arcade", + getParentRoute: () => adminRoute, + }); + const tree = rootRoute.addChildren([ + authedRoute.addChildren([adminRoute.addChildren([pluginsRoute, wired])]), + ]); + const router = createRouter({ + routeTree: tree, + history: createMemoryHistory({ initialEntries: ["/admin/plugins/arcade"] }), + }); + return render( +` from the
+ * brokered per-action verifier, `?linked=` from the GitHub account-link callback — report something
+ * that has already happened: read once, and then over. A parameter left in the address bar is read
+ * again on every reload and on every back-navigation, so news that is minutes old is redrawn as
+ * though it had just happened — somebody is told again that their account could not be connected,
+ * or that there is no authorization in progress, about an attempt they have since finished.
+ * `?linked=` was stripped and the other two were not.
+ *
+ * THE NOTICE STILL HAS TO BE DRAWN, which is the other half of the claim and the reason the strip
+ * cannot be a bare navigate on mount: the screen reads each value once into state, draws the
+ * sentence from that, and only the URL is cleaned. A test asserting an empty URL alone would be
+ * satisfied by a screen that says nothing at all.
+ */
+
+const PATH = "/settings/connected-accounts";
+
+beforeAll(() => GlobalRegistrator.register());
+afterEach(cleanup);
+afterAll(async () => {
+ // Let React's scheduler drain before the DOM globals it reads go away.
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ await GlobalRegistrator.unregister();
+});
+
+type TestRouter = { state: { location: { search: unknown; href: string } } };
+
+function renderScreen(search: string) {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+ // Seeded rather than fetched: this screen's data is not what is under test here.
+ queryClient.setQueryData(pluginKeys.page(), { servers: [], catalogue: [] });
+ queryClient.setQueryData(pluginKeys.connections(), { connections: [] });
+ queryClient.setQueryData(identityKeys.links(), []);
+
+ const root = createRootRoute({ component: () => });
+ const screen = Route.update({
+ id: PATH,
+ path: PATH,
+ getParentRoute: () => root,
+ } as never);
+ // The route tree is assembled by hand, so its types cannot describe it.
+ const router = createRouter({
+ routeTree: root.addChildren([screen]),
+ context: { queryClient },
+ history: createMemoryHistory({ initialEntries: [`${PATH}${search}`] }),
+ } as never) as never;
+ const view = render(
+
+
+ ,
+ );
+ return { view, router: router as TestRouter };
+}
+
+/** What the address bar holds now, as the router sees it. */
+const searchOf = (router: TestRouter) => router.state.location.search;
+
+test("?connected=failed is said once and then dropped from the URL", async () => {
+ const { view, router } = renderScreen("?connected=failed");
+ await view.findByText(/That account could not be connected/);
+ await waitFor(() => expect(searchOf(router)).toEqual({}));
+ // And the sentence survives the strip: the news is read once, not withdrawn.
+ expect(
+ view.queryByText(/That account could not be connected/),
+ ).not.toBeNull();
+ expect(router.state.location.href).toBe(PATH);
+});
+
+test("?authorized= is said once and then dropped from the URL", async () => {
+ const { view, router } = renderScreen("?authorized=no-attempt");
+ await view.findByText(/There is no authorization in progress/);
+ await waitFor(() => expect(searchOf(router)).toEqual({}));
+ expect(
+ view.queryByText(/There is no authorization in progress/),
+ ).not.toBeNull();
+ expect(router.state.location.href).toBe(PATH);
+});
+
+/** The one that was already stripped, kept here so the three are pinned in one place. */
+test("?linked= is said once and then dropped from the URL", async () => {
+ const { view, router } = renderScreen("?linked=failed");
+ await view.findByText(/Your GitHub account could not be linked/);
+ await waitFor(() => expect(searchOf(router)).toEqual({}));
+ expect(
+ view.queryByText(/Your GitHub account could not be linked/),
+ ).not.toBeNull();
+});
+
+/**
+ * ALL THREE AT ONCE, because stripping them one at a time is the failure mode a per-parameter
+ * effect invites: each navigate replaces the whole search, so one that drops `connected` while
+ * rebuilding `authorized` leaves the second behind for ever.
+ */
+test("all three arriving together leave nothing behind", async () => {
+ const { view, router } = renderScreen(
+ "?connected=failed&authorized=no-attempt&linked=failed",
+ );
+ await view.findByText(/That account could not be connected/);
+ await waitFor(() => expect(searchOf(router)).toEqual({}));
+ expect(router.state.location.href).toBe(PATH);
+ expect(
+ view.queryByText(/There is no authorization in progress/),
+ ).not.toBeNull();
+ expect(
+ view.queryByText(/Your GitHub account could not be linked/),
+ ).not.toBeNull();
+});
+
+/** Nothing to consume means nothing to clean up, and no navigation at all. */
+test("an arrival with no one-shot parameter is left exactly as it came", async () => {
+ const { router } = renderScreen("");
+ await waitFor(() => expect(searchOf(router)).toEqual({}));
+ expect(router.state.location.href).toBe(PATH);
+});
diff --git a/app/tests/directory-read-fallback.test.ts b/app/tests/directory-read-fallback.test.ts
new file mode 100644
index 000000000..854bf4306
--- /dev/null
+++ b/app/tests/directory-read-fallback.test.ts
@@ -0,0 +1,32 @@
+import { afterEach, expect, test } from "bun:test";
+import { composioAppsQueryOptions } from "@/lib/plugins/queries";
+
+const realFetch = globalThis.fetch;
+afterEach(() => {
+ globalThis.fetch = realFetch;
+});
+
+/** Runs the directory query against a failure that carries no server message at all. */
+async function failureMessage(provider: "composio" | "arcade") {
+ globalThis.fetch = (async () =>
+ new Response(null, { status: 500 })) as unknown as typeof fetch;
+ const options = composioAppsQueryOptions("", provider);
+ try {
+ await (options.queryFn as (ctx: unknown) => Promise)({});
+ } catch (thrown) {
+ return (thrown as Error).message;
+ }
+ throw new Error("the query was expected to fail");
+}
+
+test("a Composio deployment's messageless directory failure is the existing sentence, unchanged", async () => {
+ expect(await failureMessage("composio")).toBe(
+ "Composio's app directory could not be read.",
+ );
+});
+
+test("an Arcade deployment's messageless directory failure names Arcade and never Composio", async () => {
+ const message = await failureMessage("arcade");
+ expect(message).toBe("Arcade's app directory could not be read.");
+ expect(message).not.toMatch(/composio/i);
+});
diff --git a/app/tests/plugin-mutation-refresh.test.ts b/app/tests/plugin-mutation-refresh.test.ts
index 67c21c427..3dd72fd7f 100644
--- a/app/tests/plugin-mutation-refresh.test.ts
+++ b/app/tests/plugin-mutation-refresh.test.ts
@@ -7,6 +7,7 @@ import {
mutationOptions,
QueryClient,
} from "@tanstack/react-query";
+import { authorizeActionMutationOptions } from "../src/lib/plugins/grants";
import * as pluginMutations from "../src/lib/plugins/mutations";
import {
accountModePreview,
@@ -642,3 +643,27 @@ test("every id these writes interpolate into a URL goes through encodeURICompone
expect(raw).toEqual([]);
});
+
+/**
+ * Where the vendor's callback puts somebody down is the caller's to say, and only the default was
+ * ever pinned.
+ *
+ * The roster above drives both consent starts with no argument, so `?returnTo=settings` is all any
+ * assertion in this file has ever seen. A body that ignored its parameter and wrote `settings`
+ * itself would pass every one of them — and the defect that hides is an administrator who presses
+ * Connect on the connector's admin page, finishes consent at the vendor, and is put down in their
+ * own personal settings: the round trip the inline row exists to remove. One call each, with the
+ * value the default cannot be confused with.
+ */
+test("the consent starts carry the screen they were started from", async () => {
+ expect(
+ await requestedBy(() => connectAccountMutationOptions("admin"), HOSTILE_ID),
+ ).toEqual([`/api/plugins/servers/${ENCODED_ID}/connect?returnTo=admin`]);
+
+ expect(
+ await requestedBy(() => authorizeActionMutationOptions("admin"), {
+ serverId: HOSTILE_ID,
+ action: "GMAIL_SEND_EMAIL",
+ }),
+ ).toEqual([`/api/plugins/servers/${ENCODED_ID}/authorize?returnTo=admin`]);
+});
diff --git a/app/tests/plugin-read-failure.test.tsx b/app/tests/plugin-read-failure.test.tsx
index 4e88d3cae..9a9f062cd 100644
--- a/app/tests/plugin-read-failure.test.tsx
+++ b/app/tests/plugin-read-failure.test.tsx
@@ -84,6 +84,7 @@ function answeredWithout() {
botsMayCallBack: true,
redirectUri: null,
composioConfigured: false,
+ authorizesPerAction: false,
};
client.setQueryData(pluginKeys.page(), page);
return client;
diff --git a/app/tests/plugins-page-provider.test.ts b/app/tests/plugins-page-provider.test.ts
new file mode 100644
index 000000000..ce5e344cb
--- /dev/null
+++ b/app/tests/plugins-page-provider.test.ts
@@ -0,0 +1,26 @@
+import { expect, test } from "bun:test";
+import { type PluginsPage, pluginsProvider } from "@/lib/plugins/queries";
+
+const page = (extra: Partial): PluginsPage => ({
+ catalogue: [],
+ servers: [],
+ skills: [],
+ botsMayCallBack: false,
+ redirectUri: null,
+ composioConfigured: true,
+ authorizesPerAction: false,
+ ...extra,
+});
+
+test("a payload with no provider answers composio", () => {
+ expect(pluginsProvider(page({}))).toBe("composio");
+});
+
+test("a payload that names a provider is believed", () => {
+ expect(pluginsProvider(page({ provider: "arcade" }))).toBe("arcade");
+ expect(pluginsProvider(page({ provider: "composio" }))).toBe("composio");
+});
+
+test("no page yet answers composio", () => {
+ expect(pluginsProvider(undefined)).toBe("composio");
+});
diff --git a/app/tests/search-text.test.ts b/app/tests/search-text.test.ts
new file mode 100644
index 000000000..4a074078f
--- /dev/null
+++ b/app/tests/search-text.test.ts
@@ -0,0 +1,96 @@
+// app/tests/search-text.test.ts
+/**
+ * Search parameters that are TEXT survive the router's JSON parsing.
+ *
+ * The router JSON-parses every search value, so `?flow_id=12345` reaches a route as the number
+ * 12345 and `?flow_id={"a":1}` as an object. A route that guarded on `typeof value === "string"`
+ * dropped exactly those, which for the OAuth return leg's `flow_id` means the correlation handle
+ * vanishes for whichever ids the vendor's generator happens to emit: green in testing, and a return
+ * leg bound to nothing for a fraction of real ids in production.
+ *
+ * Two halves are pinned here. `searchText` renders whatever arrived back to the text it was, and
+ * `parseSearchText` takes away the one case it could not have rendered back exactly — a number too
+ * long to survive a double. The guard at the bottom keeps the `typeof` form from coming back.
+ */
+import { expect, test } from "bun:test";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import {
+ parseSearchText,
+ searchText,
+ searchTextEntry,
+} from "@/lib/search-text";
+import {
+ logicalLines,
+ sourceFiles,
+} from "../../server/tests/support/source-scan";
+
+test("text is recovered from whatever the router parsed the value into", () => {
+ expect(searchText("abc")).toBe("abc");
+ expect(searchText(12345)).toBe("12345");
+ expect(searchText(true)).toBe("true");
+ expect(searchText(null)).toBe("null");
+ expect(searchText({ a: 1 })).toBe('{"a":1}');
+ expect(searchText(["a"])).toBe('["a"]');
+ // Present and empty is a value, not an absence; only an absent parameter is undefined.
+ expect(searchText("")).toBe("");
+ expect(searchText(undefined)).toBeUndefined();
+});
+
+test("the entry carries the key or omits it, never sets it to undefined", () => {
+ expect(searchTextEntry("flow_id", 12345)).toEqual({ flow_id: "12345" });
+ expect(searchTextEntry("flow_id", undefined)).toEqual({});
+ expect("flow_id" in searchTextEntry("flow_id", undefined)).toBe(false);
+});
+
+/*
+ * ROUND TRIP, which is the whole claim: what the verifier put in the URL is what a route reads.
+ * `parseSearchText` is the router's own parsing, so this is the real path a browser takes.
+ */
+test("a handle survives the URL whatever it is made of", () => {
+ const handles = [
+ "fl_01HQ8Z",
+ "12345",
+ "0123",
+ "1e3",
+ "12345678901234567890",
+ '{"a":1}',
+ "true",
+ "null",
+ "a b&c/../d",
+ ];
+ for (const handle of handles) {
+ const url = `?flow_id=${encodeURIComponent(handle)}`;
+ expect(searchText(parseSearchText(url).flow_id)).toBe(handle);
+ }
+});
+
+test("a parameter that is not there stays not there", () => {
+ expect(searchText(parseSearchText("?other=1").flow_id)).toBeUndefined();
+});
+
+/**
+ * AND THE SHAPE THAT CAUSED IT DOES NOT COME BACK. A `typeof search.x === "string"` test in a
+ * route's `validateSearch` is the defect itself: it reads "keep it when it is text" and means "drop
+ * it whenever the sender's value happened to be valid JSON". Routes read text through `searchText`.
+ */
+const ROUTES_DIR = join(import.meta.dir, "../src/routes");
+const DROPPED_STRING = /typeof\s+search\??\.\w+\s*===\s*["']string["']/;
+
+test("no route reads a search parameter by guarding on its parsed type", () => {
+ // The walk and the line-splitting are the shared scanner's (server/tests/support): recursive,
+ // and LOGICAL lines — prettier wraps `typeof search.flowId ===` away from its `"string"`, and
+ // read one physical line at a time this guard would have reported green on the very shape it
+ // exists to catch.
+ const files = sourceFiles(ROUTES_DIR, [".ts", ".tsx"]);
+ expect(files.length).toBeGreaterThan(10);
+ const offenders: string[] = [];
+ for (const file of files) {
+ for (const { line, text } of logicalLines(readFileSync(file, "utf8"))) {
+ if (DROPPED_STRING.test(text)) {
+ offenders.push(`${file.slice(ROUTES_DIR.length + 1)}:${line}`);
+ }
+ }
+ }
+ expect(offenders).toEqual([]);
+});
diff --git a/app/tests/shared-connected-accounts.test.tsx b/app/tests/shared-connected-accounts.test.tsx
index 9e47756c5..f768afa3b 100644
--- a/app/tests/shared-connected-accounts.test.tsx
+++ b/app/tests/shared-connected-accounts.test.tsx
@@ -88,6 +88,7 @@ describe("Settings for a Shared app", () => {
verifiedAt: null,
probe: null,
checkable: false,
+ authorizesPerAction: false,
authScheme: "OAUTH2",
returnTo: "settings",
report: () => {},
diff --git a/app/tests/sign-resume.test.tsx b/app/tests/sign-resume.test.tsx
new file mode 100644
index 000000000..beddf0411
--- /dev/null
+++ b/app/tests/sign-resume.test.tsx
@@ -0,0 +1,303 @@
+import { afterAll, afterEach, beforeAll, expect, mock, test } from "bun:test";
+import { GlobalRegistrator } from "@happy-dom/global-registrator";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import {
+ createMemoryHistory,
+ createRootRoute,
+ createRoute,
+ createRouter,
+ Navigate,
+ Outlet,
+ RouterProvider,
+} from "@tanstack/react-router";
+import { cleanup, render, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { authKeys } from "@/lib/auth/queries";
+import { resumeUrlFor } from "@/lib/auth/resume";
+
+/*
+ * The Better Auth client is replaced at the module boundary: its fetch is captured when it is built,
+ * so a test cannot intercept the request. What this pins is what the screen hands the starter.
+ */
+type SignInSpy = (
+ provider: string,
+ start: unknown,
+ callbackURL?: string,
+) => Promise;
+type DomainSignInSpy = (
+ email: string,
+ start: unknown,
+ callbackURL?: string,
+) => Promise;
+const realAuthClient = { ...(await import("@/lib/auth/client")) };
+let signInSpy: SignInSpy = async () => {};
+let domainSignInSpy: DomainSignInSpy = async () => {};
+/*
+ * BOTH LEGS ARE SPIED, AND NEITHER IS A NO-OP. Replacing the module is how the screen is tested at
+ * all, but a replacement nobody asserts against tests nothing: `signInWithEmailDomain` stood here
+ * as `async () => {}` while the SSO form's callbackURL went unpinned, so the screen could stop
+ * passing the resume URL and this file stayed green. What the module boundary cannot see — the
+ * real client dropping a callbackURL it was handed — is pinned in `auth-client.test.ts`, which
+ * drives the real functions through their injectable starter. The two together cover the handoff.
+ */
+mock.module("@/lib/auth/client", () => ({
+ providerName: () => "Google",
+ signInWith: (...args: Parameters) => signInSpy(...args),
+ signInWithEmailDomain: (...args: Parameters) =>
+ domainSignInSpy(...args),
+}));
+const { Route } = await import("@/routes/sign");
+
+beforeAll(() => {
+ GlobalRegistrator.register();
+ // happy-dom starts at about:blank, whose origin is "null": nothing to build a callback URL on.
+ (
+ window as unknown as { happyDOM: { setURL(url: string): void } }
+ ).happyDOM.setURL("http://localhost:3010/");
+});
+afterEach(() => {
+ cleanup();
+});
+afterAll(() => {
+ // Module mocks are process-wide in bun: hand the real client back to whatever runs next.
+ mock.module("@/lib/auth/client", () => realAuthClient);
+ GlobalRegistrator.unregister();
+});
+
+/*
+ * THE HANDLE IS A VALUE, NEVER A DESTINATION: it is encoded into a path this code builds on this
+ * origin, so what arrives in a URL cannot decide where the browser goes.
+ */
+test("resumeUrlFor puts an encoded handle on the verifier, and is the origin without one", () => {
+ expect(resumeUrlFor("https://app.example", "a b&c/../d")).toBe(
+ "https://app.example/api/plugins/arcade/verify?flow_id=a%20b%26c%2F..%2Fd",
+ );
+ expect(resumeUrlFor("https://app.example", undefined)).toBe(
+ "https://app.example",
+ );
+ expect(resumeUrlFor("https://app.example", "")).toBe("https://app.example");
+ // A hostile handle stays a value on this origin.
+ const hostile = resumeUrlFor("https://app.example", "https://evil.test/");
+ expect(new URL(hostile).origin).toBe("https://app.example");
+ // Length-capped.
+ expect(
+ resumeUrlFor("https://app.example", "x".repeat(5000)).length,
+ ).toBeLessThan(600);
+});
+
+test("validateSearch keeps the handle whatever shape the router parsed it into", () => {
+ const validate = Route.options.validateSearch as (s: unknown) => object;
+ expect(validate({ flow_id: "abc" })).toEqual({ flow_id: "abc" });
+ /*
+ * THE ROUTER JSON-PARSES SEARCH VALUES, so a handle the vendor happened to mint out of digits
+ * arrives as a number and a JSON-shaped one as an object. Both are still the text the verifier
+ * sent; dropping them is losing the correlation for whichever ids the generator happens to emit.
+ */
+ expect(validate({ flow_id: 12345 })).toEqual({ flow_id: "12345" });
+ expect(validate({ flow_id: { a: 1 } })).toEqual({ flow_id: '{"a":1}' });
+ expect(validate({ flow_id: ["a"] })).toEqual({ flow_id: '["a"]' });
+ expect(validate({ flow_id: true })).toEqual({ flow_id: "true" });
+ expect("flow_id" in validate({})).toBe(false);
+});
+
+/** The whole return leg: what the verifier put in the URL is what sign-in carries back to it. */
+test("a numeric or JSON-shaped handle survives the round trip to the verifier", async () => {
+ const validate = Route.options.validateSearch as (s: unknown) => {
+ flow_id?: string;
+ };
+ const numeric = await redirectFrom(validate({ flow_id: 12345 }));
+ expect(numeric.options.href).toBe("/api/plugins/arcade/verify?flow_id=12345");
+ const shaped = await redirectFrom(validate({ flow_id: { a: 1 } }));
+ expect(shaped.options.href).toBe(
+ `/api/plugins/arcade/verify?flow_id=${encodeURIComponent('{"a":1}')}`,
+ );
+});
+
+function signedInContext() {
+ const queryClient = new QueryClient();
+ queryClient.setQueryData(authKeys.currentUser(), { id: "u1" });
+ return { queryClient };
+}
+
+async function redirectFrom(search: { flow_id?: string }) {
+ const beforeLoad = Route.options.beforeLoad as (a: unknown) => Promise;
+ try {
+ await beforeLoad({ context: signedInContext(), search });
+ } catch (thrown) {
+ return thrown as { options: { to?: string; href?: string } };
+ }
+ throw new Error("expected a redirect");
+}
+
+test("a signed-in arrival with a handle goes to the verifier, without one to /", async () => {
+ const withHandle = await redirectFrom({ flow_id: "f 1" });
+ expect(withHandle.options.href).toBe(
+ "/api/plugins/arcade/verify?flow_id=f%201",
+ );
+ const without = await redirectFrom({});
+ expect(without.options.to).toBe("/");
+ expect(without.options.href).toBeUndefined();
+});
+
+/** The sign screen on `/sign?flow_id=h1`, with whichever ways in the deployment offers. */
+function renderSignScreen(options: {
+ providers: string[];
+ sso: boolean;
+ ssoRequired: boolean;
+}) {
+ const queryClient = new QueryClient();
+ queryClient.setQueryData(authKeys.providers(), options);
+
+ queryClient.setQueryData(authKeys.currentUser(), null);
+ const root = createRootRoute();
+ const sign = Route.update({
+ id: "/sign",
+ path: "/sign",
+ getParentRoute: () => root,
+ } as never);
+ // The route tree is assembled by hand, so its types cannot describe it.
+ const router = createRouter({
+ routeTree: root.addChildren([sign]),
+ context: { queryClient },
+ history: createMemoryHistory({ initialEntries: ["/sign?flow_id=h1"] }),
+ } as never) as never;
+ const view = render(
+
+
+ ,
+ );
+ return {
+ view,
+ user: userEvent.setup({ document: view.container.ownerDocument }),
+ };
+}
+
+/** Where a press on this screen has to send the browser back to, with `?flow_id=h1` carried in. */
+const RESUME_URL = "/api/plugins/arcade/verify?flow_id=h1";
+
+test("the sign-in starter gets the resume URL as its callbackURL", async () => {
+ const asked: string[] = [];
+ signInSpy = async (_provider, _start, callbackURL) => {
+ asked.push(callbackURL ?? "");
+ };
+
+ const { view, user } = renderSignScreen({
+ providers: ["google"],
+ sso: false,
+ ssoRequired: false,
+ });
+ await user.click(await view.findByText(/Continue with Google/));
+ await waitFor(() => expect(asked).toHaveLength(1));
+ expect(asked[0]).toBe(`${window.location.origin}${RESUME_URL}`);
+});
+
+/**
+ * The company-account way in resumes too, and it was the leg nothing watched.
+ *
+ * A deployment whose people arrive through their own IdP has no provider buttons to press, so the
+ * form below them IS the sign-in for everybody there. Leaving its callbackURL unpinned meant the
+ * resume could be dropped for exactly those deployments and every test stayed green.
+ */
+test("the company-account form gets the resume URL as its callbackURL", async () => {
+ const asked: string[] = [];
+ domainSignInSpy = async (_email, _start, callbackURL) => {
+ asked.push(callbackURL ?? "");
+ };
+
+ const { view, user } = renderSignScreen({
+ providers: [],
+ sso: true,
+ ssoRequired: false,
+ });
+ await user.type(
+ await view.findByPlaceholderText("you@company.com"),
+ "someone@company.com",
+ );
+ await user.click(await view.findByText(/Continue with your company account/));
+ await waitFor(() => expect(asked).toHaveLength(1));
+ expect(asked[0]).toBe(`${window.location.origin}${RESUME_URL}`);
+});
+
+/*
+ * THE RETURN LEG HAS TO ACTUALLY ARRIVE.
+ *
+ * The verifier is an API path, not a route in this app's tree. A redirect the router is left to
+ * handle inside the app therefore matches nothing, and `__root.tsx`'s `notFoundComponent` sends an
+ * unknown address home — so somebody who was asked to sign in half-way through connecting an app
+ * signs in and lands on the home screen, with the authorization they started left behind and
+ * nothing said about it. Only a document navigation reaches the verifier at all, which is why this
+ * asserts on WHERE THE BROWSER ENDS UP rather than on the shape of the thrown redirect.
+ */
+test("a signed-in arrival with a handle lands on the verifier, not back on the home screen", async () => {
+ const queryClient = new QueryClient();
+ queryClient.setQueryData(authKeys.currentUser(), { id: "u1" });
+ queryClient.setQueryData(authKeys.providers(), {
+ providers: ["google"],
+ sso: false,
+ ssoRequired: false,
+ });
+
+ // Leaving the single-page app is an assignment to `window.location`; record it rather than ask
+ // happy-dom to fetch a path no server is listening on in this test.
+ const left: string[] = [];
+ const realLocation = window.location;
+ Object.defineProperty(window, "location", {
+ configurable: true,
+ value: {
+ origin: realLocation.origin,
+ get href() {
+ return left.at(-1) ?? `${realLocation.origin}/sign?flow_id=h1`;
+ },
+ set href(url: string) {
+ left.push(url);
+ },
+ replace: (url: string) => left.push(url),
+ assign: (url: string) => left.push(url),
+ },
+ });
+
+ const root = createRootRoute({
+ component: () => ,
+ // Mirrors app/src/routes/__root.tsx: an address this build does not know goes home.
+ notFoundComponent: () => ,
+ });
+ const home = createRoute({
+ getParentRoute: () => root,
+ path: "/",
+ component: () => home
,
+ });
+ const sign = Route.update({
+ id: "/sign",
+ path: "/sign",
+ getParentRoute: () => root,
+ } as never);
+ // The route tree is assembled by hand, so its types cannot describe it.
+ const router = createRouter({
+ routeTree: root.addChildren([home, sign]),
+ context: { queryClient },
+ history: createMemoryHistory({ initialEntries: ["/sign?flow_id=h1"] }),
+ } as never) as never;
+
+ try {
+ render(
+
+
+ ,
+ );
+ // Where the browser is: the document it was sent to, or the in-app location when it was never
+ // sent anywhere.
+ const destination = () =>
+ left.at(-1) ??
+ (router as unknown as { state: { location: { href: string } } }).state
+ .location.href;
+ await waitFor(() => expect(destination()).not.toContain("/sign"));
+ expect(destination()).toBe("/api/plugins/arcade/verify?flow_id=h1");
+ // And it got there by leaving the app, which is the only way the verifier is ever requested.
+ expect(left).toEqual(["/api/plugins/arcade/verify?flow_id=h1"]);
+ } finally {
+ Object.defineProperty(window, "location", {
+ configurable: true,
+ value: realLocation,
+ });
+ }
+});
diff --git a/app/tests/skills-read-failure.test.tsx b/app/tests/skills-read-failure.test.tsx
index 4ea13347a..47d465eda 100644
--- a/app/tests/skills-read-failure.test.tsx
+++ b/app/tests/skills-read-failure.test.tsx
@@ -90,6 +90,7 @@ function answeredEmpty() {
botsMayCallBack: true,
redirectUri: null,
composioConfigured: false,
+ authorizesPerAction: false,
};
client.setQueryData(pluginKeys.page(), page);
return client;
diff --git a/biome.json b/biome.json
index 686c6088e..df482e3e6 100644
--- a/biome.json
+++ b/biome.json
@@ -9,7 +9,8 @@
"!app/src/routeTree.gen.ts",
"!desktop/src-tauri/gen",
"!**/target",
- "!server/drizzle"
+ "!server/drizzle",
+ "!server/tests/fixtures/arcade-catalogue.json"
]
},
"formatter": {
diff --git a/bun.lock b/bun.lock
index c80a18591..d982d5df5 100644
--- a/bun.lock
+++ b/bun.lock
@@ -71,6 +71,7 @@
"@ag-ui/client": "1.0.1",
"@ag-ui/core": "1.0.1",
"@ag-ui/mastra": "1.1.6",
+ "@arcadeai/arcadejs": "2.4.1",
"@better-auth/drizzle-adapter": "^1.7.1",
"@better-auth/electron": "1.7.1",
"@better-auth/expo": "1.7.1",
@@ -197,6 +198,8 @@
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
+ "@arcadeai/arcadejs": ["@arcadeai/arcadejs@2.4.1", "", { "bin": { "arcadeai-arcadejs": "bin/cli" } }, "sha512-DLpvvDBvRNjzVmFYJ+upPrUJ8YRBoGWqPfzhuFp2+lh5Shxm0l9qLwe1Ah4pKvuCatU7TLOOTkFpICitTuuJeQ=="],
+
"@authenio/xml-encryption": ["@authenio/xml-encryption@2.0.2", "", { "dependencies": { "@xmldom/xmldom": "^0.8.6", "escape-html": "^1.0.3", "xpath": "0.0.32" } }, "sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg=="],
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
diff --git a/docker-compose.yml b/docker-compose.yml
index f59f104cd..a7c861443 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -59,7 +59,12 @@ services:
# than relying on this variable.
image: ${SERVER_IMAGE:-openbot-server:latest}
pull_policy: ${IMAGE_PULL_POLICY:-build}
- command: ["bun", "x", "drizzle-kit", "migrate", "--config=drizzle.config.ts"]
+ # The server's own migrator, which is what this image actually ships: `drizzle-kit` is a
+ # development dependency that `bun x` had to fetch at run time, and it is the one migrator that
+ # will skip a migration stamped below what the database has already recorded and still report
+ # success (see server/src/db/migration-reach.ts). The local stack is a developer's database and
+ # therefore one of the places that happens.
+ command: ["bun", "scripts/migrate.ts"]
environment:
DATABASE_URL: postgres://openbot:openbot@postgres:5432/openbot
depends_on:
diff --git a/docs/deployment.md b/docs/deployment.md
index a695fa884..354547196 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -181,6 +181,26 @@ against an empty database. The script uses the migrator inside `drizzle-orm`, wh
dependency, and keeps the same journal, so a database migrated by either is migrated. It is what
this image's own start-up path and the Helm chart's migration Job both run.
+**If a migration run refuses with "the migrator only runs migrations stamped after the newest one it
+finds"**, this database has recorded a migration stamped later than one it is now being asked to
+apply, and nothing has been applied. The migrator decides what to run by comparing journal
+timestamps against the newest timestamp in `drizzle.__drizzle_migrations` and nothing else, so a
+migration stamped below that mark would be walked past and the run reported as a success over the
+tables it should have created. The refusal names every migration in that position.
+
+It happens when a database ran a branch's migrations before that branch was merged: merging renumbers
+and restamps the branch's migrations to sit after the ones it merged in, and those merged-in
+migrations are older than the stamp the database already holds. Development, preview and reused CI
+databases are where it is met.
+
+Recreate the database from empty if it is disposable. If it is not, apply each named migration's SQL
+by hand and record it, which leaves the high-water mark where it is and lets the next run proceed:
+
+```sql
+insert into drizzle.__drizzle_migrations (hash, created_at)
+values ('.sql>', );
+```
+
## Replicas
The page snapshot a Bot resolves element references against lives in Postgres, so a second replica
diff --git a/docs/development.md b/docs/development.md
index f6da20edd..9598a23b1 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -104,6 +104,14 @@ registered in the journal, and it is the only migration anybody should be writin
CI enforces two things about this: `drizzle-kit check` for collisions and gaps between migrations,
and a generate-and-fail-if-dirty probe that refuses a schema change with no migration written for it.
+**If `db:migrate` refuses with "the migrator only runs migrations stamped after the newest one it
+finds"**, your database ran a branch's migrations before that branch was merged. Merging renumbers
+the branch's migrations to sit after the ones it merged in, and those merged-in migrations are now
+stamped older than what your database already recorded — so the migrator would skip them and report
+success over the tables they should have created. Nothing is applied; the refusal names them. Drop
+and recreate the database, which is almost always the right answer for a development one; see
+[deployment.md](deployment.md) for how to repair one you have to keep.
+
**If `drizzle-kit migrate` hangs and then exits non-zero with no error**, the journal names a
migration file that is not there. A rebase does this: `meta/_journal.json` is a checked-in file, so
restoring it can reinstate entries for migrations that were renamed. `drizzle-kit check` reports
diff --git a/docs/plugins/arcade.md b/docs/plugins/arcade.md
new file mode 100644
index 000000000..f98a5d357
--- /dev/null
+++ b/docs/plugins/arcade.md
@@ -0,0 +1,302 @@
+# Arcade
+
+Arcade is a broker, the same kind of thing Composio is: it holds people's accounts at other apps —
+Gmail, Google Calendar, GitHub, Asana and more — and publishes each app's actions as tools this
+deployment can call on somebody's behalf. A Bot with a brokered action granted reaches the app **as
+the person asking**, so two people asking the same question get the answers their own accounts can
+see. Where Arcade differs from Composio is the part an operator has to set up: **an Arcade project
+needs an OAuth app of your own for every provider, and one manual step in Arcade's dashboard.** This
+page is about those, and about what to tell somebody when a connection looks wrong.
+
+Read [Composio](composio.md) first if you have not: what a broker is, what Personal and Shared mean,
+the approval rules and the audit trail are the same here and are not repeated.
+
+Some things on this page are stated as **unproven**, and the last section lists them. Everything
+else marked _measured_ was observed against a live Arcade project.
+
+## Who sets what, and where
+
+Setting it up takes three hands, and none of them can do another's:
+
+| Who | Does | Where |
+| ---------------- | ------------------------------------------------------------ | ----------------------------------------------------------------- |
+| Whoever deploys | Sets `ARCADE_API_KEY` | The deployment's environment. There is no screen for it |
+| Whoever deploys | Sets `OPENBOT_BROKER_PROVIDER`, only where both keys are set | The deployment's environment |
+| Whoever deploys | Registers an OAuth app per provider, and sets the verifier | Arcade's dashboard, below |
+| An administrator | Enables an app and grants its actions to a Bot | `/admin/plugins/arcade`, then the app's own page, as for Composio |
+| Each person | Connects their own account to that app | `/settings/connected-accounts` |
+
+The key is an environment variable and nothing else, as Composio's is. No administrator can turn
+Arcade on from a page; `/admin/plugins/arcade` is where a key that is already set gets used.
+
+### Choosing the provider
+
+A deployment runs one broker at a time. `OPENBOT_BROKER_PROVIDER` takes `composio` or `arcade`, and
+anything else stops the server at startup.
+
+**Where exactly one of the two keys is set and the variable is unset, that provider is the active
+one, so no existing deployment has to set anything.** The variable matters only when both keys are
+set. Setting both is legal and expected, because ending an account at the old vendor needs that
+vendor's key. With both set, the variable has to say which one is live: both set and the variable
+unset stops the server at startup rather than coming up with no broker at all.
+
+**Connections already made are still read as their own provider's while nothing is active.** A
+deployment whose broker key has been withdrawn afterwards names the slice it reads from the
+`brokered_connections` rows it actually holds, so an Arcade deployment does not have its Arcade
+connections read as Composio ones, shown as absent, and left unrevokable. No broker is live in that
+state, so nothing can be connected or revoked until a provider is named. Holding rows at BOTH
+vendors with nothing active is refused rather than guessed at, which is the activation refusal from
+the other side. (Both keys set with the variable unset does not reach this: that state is refused at
+startup, above.)
+
+Three refusals protect this, and all three are deliberate:
+
+- **A named provider whose key is not set stops the server at startup.** An operator sees: _OPENBOT_BROKER_PROVIDER=arcade names a provider whose ARCADE_API_KEY is not set to a key. Set ARCADE_API_KEY to this deployment's key, or change or unset OPENBOT_BROKER_PROVIDER._ It does not fall back to a deployment with no broker, because that would send somebody looking for a broken vendor instead of at their own environment. A key that is empty or all whitespace — `ARCADE_API_KEY=` with nothing after it — is no key: that provider counts as unconfigured, and no client is ever built on an empty credential.
+- **Both keys set with the variable unset stops the server at startup.** An operator sees: _COMPOSIO_API_KEY and ARCADE_API_KEY are both set and OPENBOT_BROKER_PROVIDER is unset, so no provider is active and nothing brokered would work. Set OPENBOT_BROKER_PROVIDER to composio or arcade to say which vendor is live, or unset the key of the vendor this deployment is not using._ Nobody wants that state on purpose — the deployment would hold two vendor keys and broker nothing — and wiring no broker silently would surface much later and far away, as brokered features that simply do not work. "Both set" counts only keys that hold something: two blank keys are no keys, and are reported as such rather than as two configured vendors.
+- **Activating a provider is refused while the other one still holds connections.** It is keyed on connection rows, not on keys. The message names the provider being activated, how many connections still belong to the other, and tells the operator to keep both keys set and end those connections through the other provider's sunset flow first. Switching by editing the environment would leave accounts nobody can revoke.
+- **Holding connections at BOTH providers while nothing is active stops the server at startup.** It is the refusal above seen from the other side: with no provider named there is nothing to read one vendor's rows as, and reading them as the other's is the failure this whole section exists to prevent. The message names both vendors and asks the operator to set `OPENBOT_BROKER_PROVIDER` and to end the other provider's connections through its own sunset flow.
+
+## Setting up the Arcade project
+
+### One Arcade project per deployment
+
+Create one Arcade project for each deployment, and do not share. The verifier URL (below) is a
+setting of the **project**, and a staging deployment and a production deployment have different
+addresses, so they cannot both be pointed at by one project's setting. A project shared between them
+would have its verifier pointing at one and failing for the other.
+
+### An OAuth app of your own, per provider
+
+**Arcade's preconfigured providers cannot be used.** Register your own OAuth app at each provider
+(Google, GitHub, and so on) and give its client id and secret to Arcade as that provider's
+configuration.
+
+This was confirmed the hard way, and the reason matters because it is not a preference. A consent
+link was minted for an arbitrary `user_id` against Arcade's default Google provider. It **failed at
+verification** with _"the authorization process was not completed"_. The same consent minted for an
+Arcade project member's own identity **completed**. Arcade's built-in verifier has to recognise the
+end user as a member of the project, so it can only serve a `user_id` that is one. Per-person
+connections in OpenBot are arbitrary ids by definition — nobody is made a member of your Arcade
+project in order to connect their mailbox — so a default provider cannot serve them at all.
+
+Arcade's admin API can create the OAuth provider, so that part can be scripted.
+
+> **Decide about the agentic-client checkbox deliberately — unproven.** Google's console offers
+> _"Use this client for an AI-powered agent"_ on an OAuth client. It is a self-declaration, and
+> ticking it puts that client under a separate administrative-policy surface; Google's own advice is
+> to register separate client ids for human workflows and agent workflows. Every brokered call
+> OpenBot makes is agent-initiated, so this is a choice a deployer should make on purpose rather
+> than leave at its default. **It was not exercised:** the client used for the measurements on this
+> page was registered with the box left unticked, deliberately, to keep a policy variable out of the
+> measurement. What ticking it changes in practice is therefore not known here.
+
+### The verifier URL is the one dashboard-only step
+
+**The verifier URL cannot be automated.** It is set by hand in the Arcade dashboard, on the project,
+and there is no API that does it. It is the single manual step in setting Arcade up, and it is the
+one that is easy to do wrongly, so it is stated here rather than left to be discovered.
+
+Set it to this deployment's verifier address, then switch the project to a custom verifier. The
+address is this API's public origin — `OPENBOT_PUBLIC_URL`, which defaults to `BETTER_AUTH_URL` —
+followed by the path, so paste:
+
+```
+/api/plugins/arcade/verify
+```
+
+It is the API's origin, not the browser app's, where the two differ. A browser that arrives there
+is sent on to the connected-accounts page, with a reason beside it if the consent was refused.
+
+> **Do not save the custom-verifier setting without a URL.** It switches off the path that currently
+> works and switches on nothing — and it presents as _this deployment's own code failing_: consents
+> that used to complete stop completing, with nothing in this deployment's logs to explain it. It is
+> the trap an operator will otherwise spend an afternoon on. If connections stop completing and
+> nothing here changed, look at the project's verifier setting before anything in this deployment.
+
+## What an administrator does
+
+The shape is Composio's: enable an app, grant its actions to a Bot, and each person connects their
+own account (or an administrator connects a Shared app's account). Two things are different and
+worth reading before pressing **Add**.
+
+### Toolkits are curated or they are wrappers, and the difference is measured
+
+Every action carries a classification — whether it reads or writes, whether it is destructive — and
+what OpenBot asks before running it depends on that classification. Arcade publishes it as
+`metadata.behavior` on each tool, and toolkits fall into two groups:
+
+| Toolkit | Tools | With `metadata.behavior` |
+| ----------- | ------------------ | -----------------------: |
+| `Github` | 43 | 43 |
+| `Asana` | 19 | 19 |
+| `GithubApi` | 100 (the page cap) | **0** |
+
+Curated toolkits classify every tool. Generated API wrappers classify none. An action that is not
+classified is treated as a **destructive write**, because an unlabelled action is not claiming to be
+safe. So an administrator enabling an unclassified toolkit is warned **before** finishing that
+**every call will ask for approval** — not because anything is wrong with the toolkit, but because
+nothing in it can be told apart from a write that cannot be undone. Where a curated toolkit exists
+for the app, enable that one.
+
+## Least privilege is not an Arcade property
+
+Do not tell anybody that Arcade gives least-privilege access. It is true for some providers and
+false for others, and the same product is on both sides of the line.
+
+Arcade asks for consent per action. What that consent _covers_ depends on whether the provider
+issues per-request scopes. Measured, through a completed consent, from a clean baseline:
+
+| Provider | Scopes per action | One read consent authorized | Isolation |
+| ------------------------- | --------------------------------------------------- | --------------------------- | --------- |
+| Google (`Gmail`) | `gmail.readonly` vs `gmail.send` vs `gmail.compose` | **11 of 30** | yes |
+| GitHub (via a GitHub App) | **none, for any action** | **43 of 43** | no |
+
+The Google row has since been re-measured through this deployment's own code against the live API,
+from a clean baseline and one real consent: before consenting, **0 of 30** scope-carrying `Gmail`
+actions were authorized; after consenting to a single action (`Gmail.GetEmail`), **11 of 30** — and
+**nothing outside the scopes that consent granted**. Per-action isolation on Google holds, measured
+twice now and no longer resting on the throwaway harness that first found it.
+
+On Google, consenting to a read authorizes the reads that share its scope and nothing that sends.
+On GitHub, consenting to `Github.CountStargazers` — **a read** — flipped all 43 actions to
+authorized, **the writes and the destructive one included**, because a GitHub App's permissions are
+fixed when the app is registered, and no request can ask for fewer.
+
+> **Least privilege is a property of (provider × how its OAuth app was registered), not of Arcade.**
+> What OpenBot does is **measure which it is, per app, and say so before anybody grants anything.**
+
+So when somebody asks whether connecting an app "only lets it read", the answer is whatever that
+app's measurement says, not what Arcade says in general. The pre-redirect consent copy names the
+permission being asked for and lists the actions that share it, grouped on scopes.
+
+## Scope versus tool grant
+
+Two different questions are easily run together, and an operator will be asked both:
+
+- **Scope** answers _"may this deployment ever read your mail?"_ It is the provider's, it is given
+ when somebody consents, and it stays until it is withdrawn at the provider.
+- **The tool grant** answers _"may **this Bot** call `ListThreads`?"_ It is an administrator's
+ decision, made per Bot, per action.
+
+**Only the second is OpenBot's to give and take instantly**, and no consent widens it. Somebody
+consenting to a broad scope does not hand any Bot more actions; a Bot with no grant for an action
+cannot call it however much the account allows. On a provider that cannot narrow scopes (GitHub
+above), the tool grant is therefore the entire narrowing — the same position as Composio's.
+
+## `provider_id` means three different things
+
+An operator reading an Arcade dashboard, or an API response, or a refusal this deployment quotes,
+will meet the field `provider_id` on three different objects — and it means something different on
+each. _Measured_ against a live project:
+
+| Where you are reading it | What it is | Example |
+| --------------------------------------------------- | ------------------------------------------------ | ------------------------------------ |
+| A **tool** listing, `requirements.authorization` | the provider **type** — the vendor | `google` |
+| A **user connection** row | the provider **instance** — a configuration | `google-openbot`, `cpk-poc` |
+| An **auth provider** config | `id` is the instance, `provider_id` is the type | `id: arcade-google` of type `google` |
+
+The auth-provider config is the only bridge between the other two. Verbatim, from a live project:
+
+```
+{"id":"cpk-poc", "provider_id":"github", "type":"oauth2", "status":"active"}
+{"id":"google-openbot", "provider_id":"google", "type":"oauth2", "status":"active"}
+{"id":"arcade-google", "provider_id":"google", "type":"oauth2", "status":"active"}
+```
+
+A connection row carries **no vendor-type field at all**. Its full key set, verbatim:
+
+```
+id, provider_type, connection_id, connection_status, scopes, user_id, provider_id,
+provider_description, provider_user_info
+```
+
+`provider_type` there is `"oauth2"` — the auth **scheme**, not the vendor. So the instance id is the
+only thing on a connection that says which vendor it belongs to, and the only way from a tool's
+`google` to a person's row is through the list of configured providers of type `google`.
+
+**This bit this deployment once.** A connection lookup compared a tool's type against a row's
+instance — `"google"` against `"google-openbot"` — and so matched nothing on every real deployment,
+Arcade's own default providers included, since those are named `arcade-google` and `arcade-github`
+and never the bare type. It did not present as an error: a connection that existed read as
+**not connected**, with no account name beside it. It is fixed, and it is written down here because
+the same confusion is waiting for whoever next reads two Arcade objects side by side. **If a
+connection you can see in Arcade's dashboard looks absent in OpenBot, check that the provider the
+person connected under is one this project has configured** — a provider configuration deleted while
+somebody's connection outlived it reads the same way, and deliberately so: OpenBot leaves a row it
+cannot attribute alone rather than guess which vendor it belongs to.
+
+## Disconnecting one app may end another
+
+Arcade's connections are per **auth provider**, not per app, and the sharing is _measured_ rather
+than inferred: on a live project the provider behind both `Gmail` and `GoogleCalendar` is the single
+type `google` (`{"Gmail":["google"],"GoogleCalendar":["google"]}`), named after neither toolkit. One
+Google connection serves them both, and **no narrower instrument exists** — there is nothing to
+disconnect that ends only one of them. **Somebody disconnecting Gmail can end their Calendar**, in
+production, today.
+
+So before somebody confirms a disconnect, OpenBot names the apps that genuinely share the
+connection and would end with it. _Genuinely_ is exact: siblings are computed on the **pair (vendor
+identity, auth provider)**. Two _Shared_ apps that both use Google do **not** share a connection,
+because each Shared app has its own randomly minted identity at Arcade — disconnecting one does not
+touch the other, and the confirmation does not claim it would.
+
+## Revoking at Arcade is not revoking at the provider
+
+Google's consent screen is reached with `include_granted_scopes=true`, so **grants accumulate on the
+account**. Somebody who consents to a read and later to a write holds both at Google, as one grant.
+
+Disconnect deletes the Arcade connection. That ends **this deployment's** ability to act, and
+OpenBot asks for the upstream withdrawal as well, but what the provider then does with the standing
+grant happens out of sight. **Nobody should read Disconnect as "my Google account no longer trusts
+this."** The person who wants that has to withdraw the grant at the provider itself, and an operator asked
+about it should say so rather than reassure.
+
+## The Re-check path, and what an unverified row means
+
+Composio has a one-call probe, `askAction`, that verifies a key and which Re-check runs again.
+**It is Composio's own. Arcade has no counterpart in this release.**
+
+What an operator sees on Arcade:
+
+- Pressing **Re-check** **refuses** rather than spending a call. It does not guess at a read to try.
+- `probe_action` and `verified_at` are **not written** for an Arcade connection.
+- A row therefore carries no "last checked" date.
+
+**Nobody should read an unverified row as a broken connection.** Per-action state on Arcade is not
+cached here and compared later: it is read **live from Arcade** — whether the person has authorized
+an action is asked of Arcade when the page is drawn, so the answer on screen is Arcade's current
+one. A connection that works and has never been "checked" is the normal state, not a fault.
+
+## A consent coming back for the wrong person
+
+A consent that completes but belongs to a different identity than the attempt it is returning to is
+refused, and nothing is written either way. **What Arcade answers in that case carries no reason**:
+_measured_, confirming a return as the wrong user answers a generic `400`, `coordinator_error`,
+message `"Bad request"`, with nothing in it that names an identity mismatch.
+
+So the refusal a person sees is the ordinary **vendor-unavailable** one, not a sentence about the
+wrong account. That is a worse message and nothing more: the attempt row is left untouched, and
+the returning authorization is in any case compared to the id this deployment minted, which is the
+check that actually binds the return leg to the attempt. An operator shown a vendor-unavailable
+refusal on a consent that seemed to complete should consider the wrong-account case among the
+causes, because the vendor will not say.
+
+## What is unproven
+
+The measurements above were taken against a live Arcade project, most of them through this
+deployment's own code. What remains unproven is listed here, and nothing else on the page is.
+
+- **The agentic-client checkbox was not exercised.** Google's _"Use this client for an AI-powered
+ agent"_ declaration was deliberately left unticked for the measurements, so that it was not a
+ variable in them. Its effect on consent, scopes or policy here is not known; see the deployer note
+ above.
+- **The wording of Arcade's dashboard is not described here.** This page says to set the project's
+ verifier to the address above and does not name the dashboard's fields or screens, which were not
+ checked against it and may change. The address itself is this deployment's.
+
+## See also
+
+- [Composio](composio.md) — the other broker; Personal and Shared apps, approvals and audit work the
+ same way.
+- [Architecture](../architecture.md) — where plugins, grants, policy and audit sit.
+- [Configuration](../configuration.md) — the deployment's settings.
diff --git a/package.json b/package.json
index c4324173a..369144c19 100644
--- a/package.json
+++ b/package.json
@@ -20,11 +20,15 @@
"lint": "bunx biome lint --error-on-warnings .",
"test": "bun test",
"typecheck": "bun run --filter '*' typecheck",
+ "typecheck:tests": "bun scripts/typecheck-tests.ts",
"test:ci": "bun scripts/test-ci.ts",
+ "test:server:sharded": "bun scripts/test-server-sharded.ts",
+ "test:mutations": "bun scripts/mutation-register.ts",
"pretest": "bun run generate:app-config",
"test:smoke": "OPENBOT_SMOKE=1 bun test tests/smoke",
"test:live-screen": "OPENBOT_LIVE_SCREEN=1 bun test agent-computer/tests/live-screen.test.ts agent-computer/tests/browser-close-announcement.test.ts agent-computer/tests/reset-run.test.ts",
"test:live-composio": "bun -e \"if (!process.env.COMPOSIO_API_KEY?.trim()) { console.error('COMPOSIO_API_KEY is empty or unset, so every case in this suite would skip and this command would still exit 0. Run it as COMPOSIO_API_KEY=... bun run test:live-composio.'); process.exit(1) }\" && OPENBOT_LIVE_COMPOSIO=1 bun test server/tests/composio-live.test.ts",
+ "test:live-arcade": "bun -e \"if (!process.env.ARCADE_API_KEY?.trim()) { console.error('ARCADE_API_KEY is empty or unset, so every case in this suite would skip and this command would still exit 0. Run it as ARCADE_API_KEY=... bun run test:live-arcade.'); process.exit(1) }\" && OPENBOT_LIVE_ARCADE=1 bun test server/tests/arcade-live.test.ts",
"diagram": "bun scripts/architecture-diagram.ts",
"composio:smoke": "bun scripts/composio-smoke.ts",
"mock:knowledge": "bun scripts/mock-knowledge-mcp.ts"
diff --git a/scripts/mutation-register-preload.ts b/scripts/mutation-register-preload.ts
new file mode 100644
index 000000000..f98ac4658
--- /dev/null
+++ b/scripts/mutation-register-preload.ts
@@ -0,0 +1,35 @@
+// Applies ONE mutation-register entry to a module's source as bun imports it. See
+// `scripts/mutation-register.ts`, which spawns `bun test --preload` with this file.
+//
+// IN MEMORY, NEVER ON DISK. The loader hands bun the rewritten source; the file itself is never
+// opened for writing, so a crashed, timed-out or interrupted run leaves the tree exactly as it was.
+//
+// The mark file is how the runner tells "the suite caught the mutation" apart from "the suite never
+// imported the mutated file": it is written only when the rewrite actually happened.
+
+import { plugin } from "bun";
+import { readFileSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+import { mutate, REGISTER, REPO_ROOT } from "./mutation-register";
+
+const id = process.env.MUTATION_REGISTER_ENTRY;
+const mark = process.env.MUTATION_REGISTER_MARK;
+const entry = REGISTER.find((e) => e.id === id);
+if (!entry || !mark) {
+ throw new Error(
+ `mutation-register-preload: no register entry named ${JSON.stringify(id)}, or no mark path. Run it through scripts/mutation-register.ts.`,
+ );
+}
+const target = join(REPO_ROOT, entry.path);
+const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+
+plugin({
+ name: `mutation-register:${entry.id}`,
+ setup(build) {
+ build.onLoad({ filter: new RegExp(`^${escaped}$`) }, (args) => {
+ const contents = mutate(readFileSync(args.path, "utf8"), entry);
+ writeFileSync(mark, entry.id);
+ return { contents, loader: args.path.endsWith(".tsx") ? "tsx" : "ts" };
+ });
+ },
+});
diff --git a/scripts/mutation-register.test.ts b/scripts/mutation-register.test.ts
new file mode 100644
index 000000000..3eeb0226c
--- /dev/null
+++ b/scripts/mutation-register.test.ts
@@ -0,0 +1,94 @@
+// The register's STATIC half, cheap enough for every test run: every entry still finds the exact
+// expression it guards, its mutant still parses, and its named suites exist. Running the mutants
+// themselves is `bun run test:mutations` (scripts/mutation-register.ts) — that is the half that
+// proves the suites go red, and it spawns one `bun test` per entry, so it is not run from here.
+//
+// Why bother with this half: an entry whose expression was refactored away fails `test:mutations`
+// loudly, but only when somebody runs it. This makes the same drift fail the ordinary suite, on the
+// change that caused it, while the person who moved the code still knows what it was.
+
+import { describe, expect, test } from "bun:test";
+import { existsSync, readFileSync } from "node:fs";
+import { join } from "node:path";
+import {
+ ANCHOR_WINDOW,
+ locate,
+ type MutationEntry,
+ mutate,
+ REGISTER,
+ REPO_ROOT,
+} from "./mutation-register";
+
+const transpilers = {
+ ts: new Bun.Transpiler({ loader: "ts" }),
+ tsx: new Bun.Transpiler({ loader: "tsx" }),
+};
+
+describe("every register entry", () => {
+ test("has a unique id", () => {
+ const ids = REGISTER.map((e) => e.id);
+ expect(ids.filter((id, i) => ids.indexOf(id) !== i)).toEqual([]);
+ });
+
+ for (const entry of REGISTER) {
+ test(`${entry.id}: locates its quoted expression, and its mutant parses`, () => {
+ const source = readFileSync(join(REPO_ROOT, entry.path), "utf8");
+ expect(locate(source, entry)).not.toHaveProperty("error");
+ const mutant = mutate(source, entry);
+ expect(mutant).not.toBe(source);
+ // A syntax error would make every named test "fail" without telling any distinction apart.
+ const transpiler = entry.path.endsWith(".tsx")
+ ? transpilers.tsx
+ : transpilers.ts;
+ expect(() => transpiler.transformSync(source)).not.toThrow();
+ expect(() => transpiler.transformSync(mutant)).not.toThrow();
+ });
+
+ test(`${entry.id}: names 1-3 test files that exist`, () => {
+ expect(entry.tests.length).toBeGreaterThanOrEqual(1);
+ expect(entry.tests.length).toBeLessThanOrEqual(3);
+ for (const file of entry.tests) {
+ expect(existsSync(join(REPO_ROOT, file))).toBe(true);
+ }
+ });
+ }
+});
+
+describe("locate", () => {
+ const entry = (over: Partial): MutationEntry => ({
+ id: "x",
+ kind: "identity",
+ path: "f.ts",
+ find: "a.vendorUserId",
+ replace: "a.userId",
+ tests: ["t.test.ts"],
+ why: "",
+ ...over,
+ });
+
+ test("refuses a bare expression that occurs twice, rather than picking one", () => {
+ const source = "f(a.vendorUserId);\ng(a.vendorUserId);";
+ expect(locate(source, entry({}))).toHaveProperty("error");
+ });
+
+ test("an anchor picks the occurrence after it", () => {
+ const source = "f(a.vendorUserId);\nfunction g() { h(a.vendorUserId); }";
+ const found = locate(source, entry({ anchor: "function g() {" }));
+ expect(found).toEqual({ index: source.lastIndexOf("a.vendorUserId") });
+ });
+
+ test("does not re-aim at a sibling past the window when the guarded line is deleted", () => {
+ // The guarded expression after `function g() {` is gone; the next occurrence belongs to `k`.
+ const source = `function g() { h(); }\n${" ".repeat(ANCHOR_WINDOW)}\nfunction k() { h(a.vendorUserId); }`;
+ expect(locate(source, entry({ anchor: "function g() {" }))).toHaveProperty(
+ "error",
+ );
+ });
+
+ test("refuses an anchor that is not unique", () => {
+ const source = "function g() { a.vendorUserId }\nfunction g() {}";
+ expect(locate(source, entry({ anchor: "function g() {" }))).toHaveProperty(
+ "error",
+ );
+ });
+});
diff --git a/scripts/mutation-register.ts b/scripts/mutation-register.ts
new file mode 100644
index 000000000..15283e31d
--- /dev/null
+++ b/scripts/mutation-register.ts
@@ -0,0 +1,464 @@
+#!/usr/bin/env bun
+// The mutation register: known defects, planted on purpose, that named suites MUST catch.
+//
+// WHY THIS EXISTS. The defect class this repo's review cycles found most often is not a wrong line;
+// it is a suite that cannot tell two things apart that the code must keep apart — a local id and a
+// vendor id, a folded and an unfolded status, an absent field and an unreadable one, a scoped query
+// and an unscoped one, a call made and a call silently not made. A suite like that stays green when
+// the distinction is broken, so nothing fails and nothing changes. Each review wave checked this by
+// hand — "five planted identity defects passed 245/245" — and the check was never recorded, so the
+// next fake or fixture could re-collapse the distinction without anyone noticing. This file is that
+// hand check, written down, so it can be re-run by anyone at any time.
+//
+// WHAT A RUN DOES, per entry:
+// 1. Locates the entry's quoted expression in the target file (see "KEYED BY QUOTED EXPRESSION").
+// 2. Runs the entry's test files UNMUTATED, once per distinct file set. They must pass: a mutant
+// "caught" by a suite that was already red proves nothing.
+// 3. Runs the same files with the mutation applied IN MEMORY by `mutation-register-preload.ts`,
+// a bun loader plugin that rewrites the module's source as it is imported. The file on disk is
+// never written, so a crash, a timeout or a Ctrl-C cannot leave the tree mutated. The preload
+// leaves a mark when it actually rewrote the module; no mark means the named suites never
+// imported the file, which is the register being wrong, and it fails as such.
+// 4. A mutant is KILLED when the run reports at least one `(fail)` test. Only a failing TEST
+// counts — an import error or a crash is not a suite telling the distinction apart.
+//
+// THE VERDICT, and what each failure means:
+// - an entry with no `survivesUntil` whose mutant SURVIVES → a coverage hole. The named suite
+// cannot see this distinction any more. Fix the suite, not the register.
+// - an entry WITH `survivesUntil` whose mutant is KILLED → the debt is paid. Delete the
+// `survivesUntil` field so the entry starts guarding.
+// - an expression that cannot be located → the guarded code changed. Re-quote it deliberately.
+//
+// `survivesUntil` IS A RECORD OF DEBT, the same discipline as `server/tests/typecheck-baseline.txt`:
+// every entry carrying it is a real defect the suite cannot see today, named by the finding that
+// owns it. Removing one because the suite now catches the mutant is always correct. ADDING one to
+// an entry that was guarding is not — that is a suite losing coverage, and this check exists to say
+// so. A new entry may land with `survivesUntil` only when the hole it records is pre-existing and
+// has a finding id.
+//
+// KEYED BY QUOTED EXPRESSION, NEVER BY `path:line`. A line-keyed list goes red when someone rewraps
+// a comment three functions above the guarded code, and people learn to re-baseline it without
+// reading (`assertion-shape.test.ts`'s ALLOW list collided in three consecutive waves for exactly
+// this reason; `sole-definition.test.ts`, keyed by path and quoted expression, never has). An entry
+// names the expression itself. When the expression appears more than once in its file, `anchor`
+// names a second quoted expression that occurs EXACTLY ONCE, and the mutated expression is its first
+// occurrence after the anchor, within {@link ANCHOR_WINDOW} characters — so deleting the guarded
+// line cannot silently re-aim the mutation at a sibling further down the file.
+//
+// Usage:
+// bun run test:mutations # run every entry; exit 1 on any failure above
+// bun run test:mutations [ ...] # run only the named entries
+// bun run test:mutations --list # print the register without running anything
+//
+// Suites that need a database read TEST_DATABASE_URL from the environment, as they always do.
+
+import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+export const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+
+/** How far past its anchor an expression may start and still be the one the entry means. */
+export const ANCHOR_WINDOW = 600;
+
+/**
+ * The five ways a distinction collapses. Every entry is one of them. `casing` covers every
+ * normalization of a vendor word before it is compared: case, padding, and which words the
+ * comparison accepts at all.
+ */
+export type DistinctionKind =
+ | "identity"
+ | "casing"
+ | "absent-vs-unreadable"
+ | "query-scope"
+ | "call-made";
+
+export interface MutationEntry {
+ /** Stable name, used on the command line and in the report. */
+ id: string;
+ kind: DistinctionKind;
+ /** Repo-relative path of the production file the mutation applies to. */
+ path: string;
+ /** Optional quoted expression occurring exactly once, used to locate a non-unique `find`. */
+ anchor?: string;
+ /** The exact quoted expression the mutation replaces. */
+ find: string;
+ /** What it is replaced with. Must still parse: a syntax error is not a kill. */
+ replace: string;
+ /** Repo-relative test files, 1-3, that must go red under the mutation. */
+ tests: string[];
+ /** Why this mutation matters, and the watched red it is based on. */
+ why: string;
+ /** Finding id that owns a KNOWN hole: the mutant is expected to survive until it is fixed. */
+ survivesUntil?: string;
+}
+
+export const REGISTER: MutationEntry[] = [
+ {
+ id: "composio-revoke-asks-under-local-id",
+ kind: "identity",
+ path: "server/src/plugins/composio-client/consent.ts",
+ anchor: "async revoke({ account, toolkit }): Promise {",
+ find: "const userId = account.vendorUserId;",
+ replace: "const userId = account.userId;",
+ tests: ["server/tests/composio-adapter.test.ts"],
+ why: "A8a: withdrawal must ask Composio under the vendor id. Passed 245/245 before A8a; 18 fail after.",
+ },
+ {
+ id: "composio-is-connected-asks-under-local-id",
+ kind: "identity",
+ path: "server/src/plugins/composio-client/consent.ts",
+ anchor: "async isConnected({ account, toolkit }): Promise {",
+ find: "const userId = account.vendorUserId;",
+ replace: "const userId = account.userId;",
+ tests: ["server/tests/composio-adapter.test.ts"],
+ why: "A8a: the gate ./access asks before running an action must ask under the vendor id. 0 fail before A8a; 3 after.",
+ },
+ {
+ id: "arcade-connection-status-unfolded",
+ kind: "casing",
+ // K2: every vendor status is folded in ONE place, `vendorStatusOf` in the transport, so the
+ // per-site expressions this entry and the next once quoted are gone. Both re-quote the shared
+ // fold; each still names the suites that must see it for ITS reader.
+ path: "server/src/plugins/arcade.ts",
+ find: 'return { kind: "text", text: value.trim().toLowerCase() };',
+ replace: 'return { kind: "text", text: value };',
+ tests: [
+ "server/tests/arcade-adapter-connections.test.ts",
+ "server/tests/arcade-adapter-status.test.ts",
+ ],
+ why: "A vendor-side change of case or padding on connection_status must not change whether somebody is shown as connected.",
+ },
+ {
+ id: "arcade-mint-status-unfolded",
+ kind: "casing",
+ path: "server/src/plugins/arcade.ts",
+ find: 'return { kind: "text", text: value.trim().toLowerCase() };',
+ replace: 'return { kind: "text", text: value };',
+ tests: ["server/tests/arcade-authorize-status-engine.test.ts"],
+ why: "D6: deleting the fold on the mint's own status was reported to leave arcade-authorize-status-engine green, because :118 asserts only BrokerRefusalError, which the allowlist yields anyway. K2 added padded/re-cased allowlist rows that are handed on only when folded.",
+ },
+ {
+ id: "arcade-mint-not-started-dropped",
+ kind: "casing",
+ path: "server/src/plugins/arcade-adapter.ts",
+ find: 'const MINT_AWAITS_CONSENT_STATUSES = new Set(["not_started", "pending"]);',
+ replace: 'const MINT_AWAITS_CONSENT_STATUSES = new Set(["pending"]);',
+ tests: ["server/tests/arcade-authorize-status-engine.test.ts"],
+ why: "D6: not_started, half the allowlist, is untested — a mint that has not begun would be refused as if it were finished. K2 added the not_started rows.",
+ },
+ {
+ id: "arcade-unreadable-authorization-read-as-none",
+ kind: "absent-vs-unreadable",
+ // K2 moved `authorizationRequirementOf` into the transport, which both readers import.
+ path: "server/src/plugins/arcade.ts",
+ anchor:
+ 'if (typeof authorization !== "object" || Array.isArray(authorization)) {',
+ find: 'kind: "unreadable",',
+ replace: 'kind: "none",',
+ tests: [
+ "server/tests/arcade-adapter-status.test.ts",
+ "server/tests/arcade-auth-provider.test.ts",
+ "server/tests/arcade-adapter-connections.test.ts",
+ ],
+ why: "C2/C2b: an authorization block that cannot be read is not the vendor saying none — reading it as none silently drops the consent requirement.",
+ },
+ {
+ id: "arcade-unreadable-scopes-read-as-none",
+ kind: "absent-vs-unreadable",
+ path: "server/src/plugins/arcade.ts",
+ find: 'return { kind: "unreadable", field: "scopes", shape: shapeOf(scopes) };',
+ replace: 'return { kind: "read", scopes: [] };',
+ tests: ["server/tests/arcade-vendor-read-contract.test.ts"],
+ why: "K2 (C2c): an unreadable oauth2 scopes value read as [] is 'the connection is the grant' on the gate, the catalogue and the grants screen at once.",
+ },
+ {
+ id: "arcade-missing-token-status-read-as-held",
+ kind: "absent-vs-unreadable",
+ path: "server/src/plugins/arcade-adapter.ts",
+ find: 'if (tokenStatus.kind !== "text") {',
+ replace:
+ 'if (tokenStatus.kind !== "text") return [{ action, state: "authorized", scopes, authorization: "held" }]; if (false) {',
+ tests: ["server/tests/arcade-vendor-read-contract.test.ts"],
+ why: "K2 (A7c): a token_status the listing did not give read as a held grant marked every action of a configured provider authorized for everyone.",
+ },
+ {
+ id: "arcade-unreadable-status-read-as-absent",
+ kind: "absent-vs-unreadable",
+ path: "server/src/plugins/arcade.ts",
+ find: 'return { kind: "unreadable", shape: shapeOf(value) };',
+ replace: 'return { kind: "absent" };',
+ tests: [
+ "server/tests/arcade-vendor-read-contract.test.ts",
+ "server/tests/arcade-authorize-status-engine.test.ts",
+ ],
+ why: "K2 (A7b, L2-b): a present-but-unreadable vendor status read as silence makes a connection live and hands a mint's bearer url on as a redirect.",
+ },
+ {
+ id: "arcade-auth-providers-skip-unreadable",
+ kind: "absent-vs-unreadable",
+ path: "server/src/plugins/arcade-adapter.ts",
+ find: "unreadable ??= { action, field: read.field, shape: read.shape };",
+ replace: "void action;",
+ tests: [
+ "server/tests/arcade-adapter-connections.test.ts",
+ "server/tests/arcade-auth-provider.test.ts",
+ ],
+ why: "C2b: restoring the silent skip of an unreadable authorization in authProvidersNamedBy turned 3 refusals back into answers (35 pass / 3 fail).",
+ },
+ {
+ id: "composio-account-listing-unscoped-by-person",
+ kind: "query-scope",
+ path: "server/src/plugins/composio-client/context.ts",
+ find: "userIds: [userId],",
+ replace: "",
+ tests: ["server/tests/composio-adapter.test.ts"],
+ why: "A8a: an account listing without its userIds filter reads everybody's accounts as this person's. 5 fail before A8a's honest fakes; 23 after.",
+ },
+ {
+ id: "arcade-connection-listing-unscoped-by-person",
+ kind: "query-scope",
+ path: "server/src/plugins/arcade-adapter.ts",
+ anchor: "client.admin.userConnections.list({",
+ find: "user_id: vendorUserId,",
+ replace: "",
+ tests: [
+ "server/tests/arcade-adapter-connections.test.ts",
+ "server/tests/arcade-adapter.test.ts",
+ ],
+ why: "A6b: the connection listing must be scoped to this person, or somebody else's connection answers isConnected and is what revoke deletes.",
+ },
+ {
+ id: "disconnect-never-asks-the-vendor",
+ kind: "call-made",
+ path: "server/src/plugins/plugin-store/connections.ts",
+ find: "const ended = await broker.revoke({",
+ replace:
+ "const ended = await ((_skipped: unknown) => Promise.resolve(false))({",
+ tests: [
+ "server/tests/arcade-disconnect-route.test.ts",
+ "server/tests/arcade-disconnect-cascade.test.ts",
+ ],
+ why: "C8c: a disconnect that never asks the vendor to withdraw leaves a live account behind a deleted local row.",
+ },
+];
+
+/** Where in `source` the entry's expression starts, or a sentence saying why it cannot be found. */
+export function locate(
+ source: string,
+ entry: MutationEntry,
+): { index: number } | { error: string } {
+ const count = (needle: string) => source.split(needle).length - 1;
+ if (entry.anchor === undefined) {
+ const n = count(entry.find);
+ if (n === 1) return { index: source.indexOf(entry.find) };
+ return {
+ error:
+ n === 0
+ ? `${entry.path} no longer contains ${JSON.stringify(entry.find)}. The guarded code changed; re-quote the entry deliberately.`
+ : `${entry.path} contains ${JSON.stringify(entry.find)} ${n} times. Give the entry an anchor that occurs exactly once.`,
+ };
+ }
+ const anchors = count(entry.anchor);
+ if (anchors !== 1) {
+ return {
+ error: `${entry.path} contains the anchor ${JSON.stringify(entry.anchor)} ${anchors} times; it must occur exactly once.`,
+ };
+ }
+ const after = source.indexOf(entry.anchor) + entry.anchor.length;
+ const index = source.indexOf(entry.find, after);
+ if (index === -1 || index - after > ANCHOR_WINDOW) {
+ return {
+ error: `${entry.path} has no ${JSON.stringify(entry.find)} within ${ANCHOR_WINDOW} characters after ${JSON.stringify(entry.anchor)}. The guarded code changed; re-quote the entry deliberately.`,
+ };
+ }
+ return { index };
+}
+
+/** `source` with the entry's mutation applied. Throws when the expression cannot be located. */
+export function mutate(source: string, entry: MutationEntry): string {
+ const found = locate(source, entry);
+ if ("error" in found) throw new Error(found.error);
+ return (
+ source.slice(0, found.index) +
+ entry.replace +
+ source.slice(found.index + entry.find.length)
+ );
+}
+
+interface Run {
+ exitCode: number;
+ failed: number;
+ failNames: string[];
+ output: string;
+}
+
+const RUN_TIMEOUT_MS = 300_000;
+
+function runTests(files: string[], env: Record): Run {
+ const preload = join(REPO_ROOT, "scripts", "mutation-register-preload.ts");
+ const args = ["bun", "test"];
+ if (env.MUTATION_REGISTER_ENTRY) args.push("--preload", preload);
+ const proc = Bun.spawnSync([...args, ...files], {
+ cwd: REPO_ROOT,
+ env: { ...process.env, ...env },
+ stdout: "pipe",
+ stderr: "pipe",
+ timeout: RUN_TIMEOUT_MS,
+ });
+ const output = `${proc.stdout.toString()}\n${proc.stderr.toString()}`;
+ const failNames = output
+ .split("\n")
+ .filter((line) => line.startsWith("(fail)"))
+ .map((line) =>
+ line.replace(/^\(fail\)\s*/, "").replace(/\s*\[[^\]]*\]$/, ""),
+ );
+ return {
+ exitCode: proc.exitCode ?? -1,
+ failed: failNames.length,
+ failNames,
+ output,
+ };
+}
+
+type Verdict =
+ | "killed"
+ | "survived"
+ | "not-reached"
+ | "unlocatable"
+ | "control-red";
+
+interface Outcome {
+ entry: MutationEntry;
+ verdict: Verdict;
+ ok: boolean;
+ detail: string;
+}
+
+function judge(
+ entry: MutationEntry,
+ verdict: Verdict,
+ detail: string,
+): Outcome {
+ if (verdict === "killed") {
+ return entry.survivesUntil
+ ? {
+ entry,
+ verdict,
+ ok: false,
+ detail: `${detail}. STALE DEBT: the suite now catches this; delete survivesUntil "${entry.survivesUntil}".`,
+ }
+ : { entry, verdict, ok: true, detail };
+ }
+ if (verdict === "survived" && entry.survivesUntil) {
+ return {
+ entry,
+ verdict,
+ ok: true,
+ detail: `known hole, owned by ${entry.survivesUntil}`,
+ };
+ }
+ return { entry, verdict, ok: false, detail };
+}
+
+function main(argv: string[]): number {
+ if (argv.includes("--list")) {
+ for (const e of REGISTER) {
+ console.log(
+ `${e.id} [${e.kind}] ${e.path}${e.survivesUntil ? ` survivesUntil ${e.survivesUntil}` : ""}`,
+ );
+ }
+ return 0;
+ }
+ const wanted = argv.filter((a) => !a.startsWith("--"));
+ const unknown = wanted.filter((id) => !REGISTER.some((e) => e.id === id));
+ if (unknown.length > 0) {
+ console.error(`No register entry named ${unknown.join(", ")}.`);
+ return 1;
+ }
+ const entries = wanted.length
+ ? REGISTER.filter((e) => wanted.includes(e.id))
+ : REGISTER;
+
+ const controls = new Map();
+ const outcomes: Outcome[] = [];
+ const markDir = mkdtempSync(join(tmpdir(), "mutation-register-"));
+ try {
+ for (const entry of entries) {
+ const target = join(REPO_ROOT, entry.path);
+ const before = readFileSync(target, "utf8");
+ const found = locate(before, entry);
+ if ("error" in found) {
+ outcomes.push(judge(entry, "unlocatable", found.error));
+ continue;
+ }
+ const key = [...entry.tests].sort().join(" ");
+ let control = controls.get(key);
+ if (!control) {
+ process.stdout.write(`control ${key} ... `);
+ control = runTests(entry.tests, {});
+ controls.set(key, control);
+ console.log(control.exitCode === 0 ? "green" : "RED");
+ }
+ if (control.exitCode !== 0) {
+ outcomes.push(
+ judge(
+ entry,
+ "control-red",
+ `the named suites fail WITHOUT the mutation (${control.failed} failing), so a kill would prove nothing:\n${control.failNames.join("\n")}`,
+ ),
+ );
+ continue;
+ }
+ const mark = join(markDir, entry.id);
+ process.stdout.write(`mutant ${entry.id} ... `);
+ const run = runTests(entry.tests, {
+ MUTATION_REGISTER_ENTRY: entry.id,
+ MUTATION_REGISTER_MARK: mark,
+ });
+ if (readFileSync(target, "utf8") !== before) {
+ // The preload never writes, so this is a bug in the register itself. Say so loudly.
+ throw new Error(
+ `${entry.path} changed on disk during the mutant run. Inspect it with git diff.`,
+ );
+ }
+ let outcome: Outcome;
+ if (!existsSync(mark)) {
+ outcome = judge(
+ entry,
+ "not-reached",
+ `the named suites never imported ${entry.path}, so the mutation was never applied. Name a suite that exercises it.\n${run.output.slice(-2000)}`,
+ );
+ } else if (run.failed > 0) {
+ outcome = judge(entry, "killed", `${run.failed} test(s) failed`);
+ } else {
+ outcome = judge(
+ entry,
+ "survived",
+ run.exitCode === 0
+ ? "every named test still passes with the distinction broken: COVERAGE HOLE"
+ : `no test failed (exit ${run.exitCode}); a crash is not a kill:\n${run.output.slice(-2000)}`,
+ );
+ }
+ console.log(outcome.verdict.toUpperCase());
+ outcomes.push(outcome);
+ }
+ } finally {
+ rmSync(markDir, { recursive: true, force: true });
+ }
+
+ console.log("");
+ for (const o of outcomes) {
+ console.log(
+ `${o.ok ? "ok " : "FAIL"} ${o.verdict.padEnd(11)} ${o.entry.id} — ${o.detail}`,
+ );
+ }
+ const failures = outcomes.filter((o) => !o.ok).length;
+ console.log(
+ `\n${outcomes.length - failures} ok, ${failures} failing, of ${outcomes.length} entries.`,
+ );
+ return failures === 0 ? 0 : 1;
+}
+
+if (import.meta.main) process.exit(main(process.argv.slice(2)));
diff --git a/scripts/test-server-sharded.ts b/scripts/test-server-sharded.ts
new file mode 100644
index 000000000..81d5c3ecf
--- /dev/null
+++ b/scripts/test-server-sharded.ts
@@ -0,0 +1,140 @@
+export {};
+
+/**
+ * `server/tests` in shards, because the whole directory in one process no longer runs.
+ *
+ * Around a hundred of those files open a module-scope connection pool. PostgreSQL's default
+ * `max_connections` is 100, so a single `bun test server/tests` exhausts the server's connection
+ * slots during the import phase and dies with `sorry, too many clients already` BEFORE it reports
+ * anything. That is a harness ceiling, not a regression, and it is never a reason to change source.
+ *
+ * Shards keep the pools-per-process count under the ceiling. The suite is split into fixed-size
+ * groups of files and each group runs in its own `bun test` invocation, so each process opens its
+ * own pools and gives them back when it exits.
+ *
+ * Why this script exists at all, rather than a remembered shell incantation: a sharded sweep is the
+ * only thing that has ever caught a test file that went red and stayed red. A targeted run passes,
+ * `typecheck` passes (`server/tests` is outside `tsc`), a reviewer reads the diff and sees nothing,
+ * and the branch is red the whole time. The sweep belongs at every quality gate, and a check that
+ * has to be retyped from memory at every gate is a check that gets skipped.
+ *
+ * Two deliberate properties:
+ *
+ * - **Every shard's summary line is read, and the exit code comes from `bun test` itself.** Piping
+ * bun's output through `grep` or `tail` overwrites `$?` with the pipe's status, which is how a
+ * failing shard reports success. Each shard's stderr is captured and echoed VERBATIM so a failure
+ * message survives in full for diagnosis.
+ * - **A shard whose summary cannot be parsed fails the run.** A pass that cannot be counted is not
+ * a pass; an import-time throw takes its file's tests with it and reports no failure.
+ *
+ * Usage: `bun run test:server:sharded`, with `TEST_DATABASE_URL` set for the integration suites.
+ * `SHARD_SIZE` overrides the files-per-shard count.
+ */
+
+const SHARD_SIZE = Number.parseInt(process.env.SHARD_SIZE ?? "55", 10);
+
+async function writeStderr(text: string) {
+ if (!text) return;
+ if (process.stderr.write(text)) return;
+ await new Promise((resolve) => process.stderr.once("drain", resolve));
+}
+
+if (!process.env.TEST_DATABASE_URL?.trim()) {
+ await writeStderr(
+ "TEST_DATABASE_URL is empty or unset. The integration suites would each fail to connect, which\n" +
+ "looks like a hundred broken tests rather than one missing variable. Run this as\n" +
+ "TEST_DATABASE_URL=postgres://openbot:openbot@localhost:5432/openbot_test bun run test:server:sharded\n",
+ );
+ process.exit(1);
+}
+
+const files = (
+ await Array.fromAsync(
+ new Bun.Glob("server/tests/**/*.test.ts").scan({ cwd: process.cwd() }),
+ )
+).sort();
+
+if (files.length === 0) {
+ await writeStderr(
+ "No test files matched server/tests/**/*.test.ts. Refusing to report a pass.\n",
+ );
+ process.exit(1);
+}
+
+const shards: string[][] = [];
+for (let index = 0; index < files.length; index += SHARD_SIZE) {
+ shards.push(files.slice(index, index + SHARD_SIZE));
+}
+
+// `bun run generate:app-config` once, up front, rather than per shard: the `pretest` hook that
+// normally produces it fires for `bun run test`, and these shards invoke `bun test` directly.
+const config = Bun.spawn(["bun", "run", "generate:app-config"], {
+ stdout: "inherit",
+ stderr: "inherit",
+});
+if ((await config.exited) !== 0) {
+ await writeStderr(
+ "\nCould not generate the application config, so route imports would fail.\n",
+ );
+ process.exit(1);
+}
+
+await writeStderr(
+ `\nRunning ${files.length} files in ${shards.length} shards of up to ${SHARD_SIZE}.\n`,
+);
+
+let passed = 0;
+let failed = 0;
+let skipped = 0;
+const unreadable: number[] = [];
+const red: number[] = [];
+
+for (const [index, shard] of shards.entries()) {
+ await writeStderr(
+ `\n--- shard ${index + 1}/${shards.length} (${shard.length} files) ---\n`,
+ );
+
+ const proc = Bun.spawn(["bun", "test", ...shard], {
+ stdout: "inherit",
+ stderr: "pipe",
+ });
+
+ // Bun writes its summary to stderr, so it is captured to be parsed, then echoed unmodified.
+ const stderr = await new Response(proc.stderr).text();
+ await writeStderr(stderr);
+
+ // Read the status from the process, never from the text: a parse failure and a test failure are
+ // different outcomes and both have to be reported.
+ const exitStatus = await proc.exited;
+ if (exitStatus !== 0) red.push(index + 1);
+
+ const passMatch = stderr.match(/(\d+) pass/);
+ const failMatch = stderr.match(/(\d+) fail/);
+ const skipMatch = stderr.match(/(\d+) skip/);
+
+ if (!passMatch) {
+ unreadable.push(index + 1);
+ } else {
+ passed += Number.parseInt(passMatch[1] as string, 10);
+ failed += failMatch ? Number.parseInt(failMatch[1] as string, 10) : 0;
+ skipped += skipMatch ? Number.parseInt(skipMatch[1] as string, 10) : 0;
+ }
+}
+
+await writeStderr(
+ `\n=== ${shards.length} shards: ${passed} pass / ${failed} fail / ${skipped} skip ===\n`,
+);
+
+if (red.length > 0) {
+ await writeStderr(`Shards that exited non-zero: ${red.join(", ")}.\n`);
+ process.exitCode = 1;
+}
+
+if (unreadable.length > 0) {
+ await writeStderr(
+ `Could not read a summary line from shard(s) ${unreadable.join(", ")}. Refusing to report a\n` +
+ "pass on a run that cannot be counted: the usual cause is a file that threw while being\n" +
+ "imported, which takes its tests with it and reports nothing.\n",
+ );
+ process.exitCode = 1;
+}
diff --git a/scripts/typecheck-tests.ts b/scripts/typecheck-tests.ts
new file mode 100644
index 000000000..4649ec853
--- /dev/null
+++ b/scripts/typecheck-tests.ts
@@ -0,0 +1,150 @@
+#!/usr/bin/env bun
+// Type-check `server/tests` against a frozen baseline of the errors that were already there.
+//
+// WHY A BASELINE AND NOT A CLEAN GATE. `server/tsconfig.json` does not include `tests`, and the
+// runner is bun, which erases types rather than checking them, so `bun run typecheck` reports CLEAN
+// while real defects sit in the suite — broken `import type` paths that silently degrade a value to
+// `any` (including in a shared helper, which voids its type guarantee for every file that uses it),
+// stubs missing methods their interface requires, `number | undefined` passed where `number` is
+// required. Turning the check on is one change; fixing what it finds is another, much larger one.
+// Freezing the known set lets the check land green and start earning its keep immediately: a NEW
+// error fails the build from today, and the existing ones get fixed on their own schedule.
+//
+// WHY THE PROJECT'S OWN CONFIG. The check runs `tsc -p server/tsconfig.tests.json`, which extends
+// `server/tsconfig.json`. Checking these files with an ad-hoc `tsc file.ts` plus hand-written flags
+// is unsound: it drags `src` in under options the project does not use and emits a flood of errors
+// that are artefacts of the flags rather than defects. Any baseline produced that way is garbage.
+//
+// Usage:
+// bun scripts/typecheck-tests.ts # check against the baseline; exit 1 on a new error
+// bun scripts/typecheck-tests.ts --update # rewrite the baseline from the current output
+
+import { existsSync, readFileSync, writeFileSync } from "node:fs";
+import { dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const project = join("server", "tsconfig.tests.json");
+const baselinePath = join(
+ repoRoot,
+ "server",
+ "tests",
+ "typecheck-baseline.txt",
+);
+
+const header = [
+ "# Frozen type errors in `server/tests`, as reported by",
+ "# bunx tsc --noEmit -p server/tsconfig.tests.json",
+ "# One line per error: :: error TS: . Column and the indented",
+ "# elaboration lines are dropped so the baseline does not churn on reformatting.",
+ "#",
+ "# This file is a RECORD OF DEBT, not a list of things that are fine. Every line is a real defect",
+ "# that the suite could not see because it was never type-checked. Fixing one and deleting its",
+ "# line is always correct. ADDING a line is not: a new error means code written today is wrong,",
+ "# and the gate exists to say so. Regenerate with `bun scripts/typecheck-tests.ts --update` only",
+ "# after a deliberate decision that the new entries are pre-existing debt being surfaced rather",
+ "# than debt being created.",
+ "#",
+ "# Sorted. Duplicates are significant: a repeated line means that many identical errors.",
+];
+
+// `path(line,col): error TScode: message` — the first line of a diagnostic. Elaborations are
+// indented, so anchoring on a non-space first character drops them.
+const DIAGNOSTIC = /^(\S[^(]*)\((\d+),\d+\): error (TS\d+): (.*)$/;
+
+function runTsc(): string {
+ const result = Bun.spawnSync({
+ cmd: ["bunx", "tsc", "--noEmit", "--pretty", "false", "-p", project],
+ cwd: repoRoot,
+ stdout: "pipe",
+ stderr: "pipe",
+ });
+ return `${result.stdout.toString()}${result.stderr.toString()}`;
+}
+
+function parse(output: string): string[] {
+ const errors: string[] = [];
+ for (const raw of output.split("\n")) {
+ const match = DIAGNOSTIC.exec(raw.trimEnd());
+ if (!match) continue;
+ const [, file, line, code, message] = match;
+ // tsc prints the project-relative path in the location, but absolute paths inside some
+ // messages (TS6142 names the resolved file). Strip the checkout prefix so the baseline is
+ // the same on a laptop, in a worktree and on CI.
+ const normalized = `${file}:${line}: error ${code}: ${message}`.replaceAll(
+ `${repoRoot}/`,
+ "",
+ );
+ errors.push(normalized);
+ }
+ return errors.sort();
+}
+
+function readBaseline(): string[] {
+ if (!existsSync(baselinePath)) return [];
+ return readFileSync(baselinePath, "utf8")
+ .split("\n")
+ .filter((line) => line.trim() !== "" && !line.startsWith("#"))
+ .sort();
+}
+
+// Multiset difference: three identical errors in the baseline and four in the output is ONE new
+// error, not zero. A set would swallow it.
+function subtract(from: string[], remove: string[]): string[] {
+ const counts = new Map();
+ for (const line of remove) counts.set(line, (counts.get(line) ?? 0) + 1);
+ const left: string[] = [];
+ for (const line of from) {
+ const n = counts.get(line) ?? 0;
+ if (n > 0) counts.set(line, n - 1);
+ else left.push(line);
+ }
+ return left;
+}
+
+const output = runTsc();
+const actual = parse(output);
+
+// A tsc crash, a bad config or a missing dependency produces output with no parseable diagnostic
+// and a non-empty body. Treat that as a failure rather than as "zero errors, all good" — a gate
+// that goes green when its checker did not run is the defect this whole exercise exists to kill.
+if (actual.length === 0 && output.trim() !== "") {
+ console.error("tsc produced output but no parseable diagnostics:\n");
+ console.error(output);
+ process.exit(1);
+}
+
+if (process.argv.includes("--update")) {
+ writeFileSync(baselinePath, `${[...header, ...actual].join("\n")}\n`);
+ console.log(`Wrote ${actual.length} baseline entries to ${baselinePath}`);
+ process.exit(0);
+}
+
+const baseline = readBaseline();
+const added = subtract(actual, baseline);
+const fixed = subtract(baseline, actual);
+
+if (fixed.length > 0) {
+ // Not a failure. Fixing an error must never break the build; it only makes the baseline stale.
+ console.log(
+ `${fixed.length} baselined error(s) no longer occur. Drop them from the baseline:`,
+ );
+ for (const line of fixed) console.log(` - ${line}`);
+ console.log("");
+}
+
+if (added.length > 0) {
+ console.error(
+ `${added.length} NEW type error(s) in server/tests, not in the baseline:\n`,
+ );
+ for (const line of added) console.error(` ${line}`);
+ console.error(
+ "\nFix them. Do not run --update to silence them: the baseline records debt that predates" +
+ "\nthe gate, and an error reported here was introduced after it.",
+ );
+ process.exit(1);
+}
+
+console.log(
+ `server/tests type-check clean against the baseline (${actual.length} known error(s)).`,
+);
diff --git a/server/drizzle/0057_arcade_provider.sql b/server/drizzle/0057_arcade_provider.sql
new file mode 100644
index 000000000..99b312fa4
--- /dev/null
+++ b/server/drizzle/0057_arcade_provider.sql
@@ -0,0 +1,16 @@
+CREATE TABLE "pending_authorizations" (
+ "actor_id" text PRIMARY KEY NOT NULL,
+ "server_id" text NOT NULL,
+ "action" text NOT NULL,
+ "holder" text NOT NULL,
+ "user_id" text,
+ "vendor_user_id" text NOT NULL,
+ "auth_id" text,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "completed_at" timestamp with time zone,
+ "outcome" text
+);
+--> statement-breakpoint
+ALTER TABLE "brokered_connections" DROP CONSTRAINT "brokered_connections_provider_check";--> statement-breakpoint
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_server_id_mcp_servers_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."mcp_servers"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "brokered_connections" ADD CONSTRAINT "brokered_connections_provider_check" CHECK ("brokered_connections"."provider" IN ('composio', 'arcade'));
\ No newline at end of file
diff --git a/server/drizzle/0058_pending_authorization_checks.sql b/server/drizzle/0058_pending_authorization_checks.sql
new file mode 100644
index 000000000..a940de2bd
--- /dev/null
+++ b/server/drizzle/0058_pending_authorization_checks.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_holder_check" CHECK (("pending_authorizations"."holder" = 'person' AND "pending_authorizations"."user_id" IS NOT NULL) OR ("pending_authorizations"."holder" = 'deployment' AND "pending_authorizations"."user_id" IS NULL));--> statement-breakpoint
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_outcome_check" CHECK ("pending_authorizations"."outcome" IS NULL OR "pending_authorizations"."outcome" IN ('completed', 'failed', 'unreconciled', 'withdrawn'));
\ No newline at end of file
diff --git a/server/drizzle/0059_pending_authorization_completion_pair.sql b/server/drizzle/0059_pending_authorization_completion_pair.sql
new file mode 100644
index 000000000..54e7e59d8
--- /dev/null
+++ b/server/drizzle/0059_pending_authorization_completion_pair.sql
@@ -0,0 +1 @@
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_completion_check" CHECK (("pending_authorizations"."completed_at" IS NULL) = ("pending_authorizations"."outcome" IS NULL));
\ No newline at end of file
diff --git a/server/drizzle/0060_pending_authorization_person_keys.sql b/server/drizzle/0060_pending_authorization_person_keys.sql
new file mode 100644
index 000000000..b13e24595
--- /dev/null
+++ b/server/drizzle/0060_pending_authorization_person_keys.sql
@@ -0,0 +1,23 @@
+-- `pending_authorizations.actor_id` and `.user_id` named `users.id` from the day 0057 created the
+-- table and carried no key, so a deleted person left attempt rows pointing at nobody. Both get the
+-- key here, both CASCADE: an attempt is in-flight working state with a fifteen-minute life, holding
+-- no grant and no secret, and what a person authorized is recorded in `audit_events`, which is
+-- keyless on purpose so the trail outlives them. RESTRICT would instead make somebody undeletable
+-- while a browser of theirs sat mid-redirect.
+--
+-- THE ORPHANS GO FIRST, because `ADD CONSTRAINT` is not conditional: on any deployment that has run
+-- 0057 and since deleted a person, the statement below would stop dead on rows the column was never
+-- able to refuse. They are deleted rather than repaired — there is nothing to repair a row to, and
+-- nulling `user_id` would trip `pending_authorizations_holder_check` on a `person` attempt. Nothing
+-- reads an attempt whose person is gone; the verifier finds attempts from a live session.
+DELETE FROM "pending_authorizations"
+WHERE NOT EXISTS (SELECT 1 FROM "users" WHERE "users"."id" = "pending_authorizations"."actor_id")
+ OR ("pending_authorizations"."user_id" IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM "users" WHERE "users"."id" = "pending_authorizations"."user_id"));--> statement-breakpoint
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_actor_id_users_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+-- A foreign key is not indexed on the referencing side, so until now deleting one `mcp_servers` row
+-- made 0057's cascade scan this table, and so did every lookup by server. `user_id` gets one for
+-- the same reason, now that it is a key too.
+CREATE INDEX "pending_authorizations_server_idx" ON "pending_authorizations" USING btree ("server_id");--> statement-breakpoint
+CREATE INDEX "pending_authorizations_user_idx" ON "pending_authorizations" USING btree ("user_id");
diff --git a/server/drizzle/0061_settle_brokered_account_mode.sql b/server/drizzle/0061_settle_brokered_account_mode.sql
new file mode 100644
index 000000000..c22d4d725
--- /dev/null
+++ b/server/drizzle/0061_settle_brokered_account_mode.sql
@@ -0,0 +1,30 @@
+-- Every brokered app that HAS an account says whose, on the deployments where one already does not.
+--
+-- `addBrokeredApp` settles `account_mode` on its INSERT and, from this change on, after its upsert
+-- as well. Its CONFLICT arm never did, so an enable that landed on a row that was not brokered —
+-- one added by hand, restored, or left by a build that spelled ids differently — converted it to a
+-- brokered app and left this column null. `accountFor` refuses a call against such a row as
+-- ambiguous ("does not say whether it is reached through each person's account or a shared one"),
+-- so the app is enabled, listed, connectable and dead.
+--
+-- THIS RUNS BECAUSE THE CODE FIX CANNOT REACH THOSE ROWS. An app that is already enabled cannot be
+-- added again without being removed first — the Plugins page's Add is a switch, and switching it
+-- off deletes the app and every connection on it — so "press Add again" is not a remedy an
+-- administrator can perform, and the Plugins page offers no way to say Personal: it reads any
+-- non-`shared` value as Personal already and shows only "Make shared". A fix in the writer alone
+-- would leave every deployment that is already in this state in it forever.
+--
+-- `personal` is what the insert writes for a new app and the conservative answer: each person
+-- reaches their own account. `shared` would hand everybody one account nobody chose to share.
+--
+-- ONLY ROWS WITH AN ACCOUNT TO HOLD, and `NO_AUTH` is the one scheme with none — `schemeKind`'s
+-- `none`, the same set `mcp_servers.account_mode` is documented as null for. A null `auth_scheme`
+-- is NOT in that set: it is `unreadable`, which `accountFor` treats as having an account, so such a
+-- row is refused exactly as a recorded `OAUTH2` one is and needs the mode just as much.
+--
+-- ONLY ROWS THAT SAY NOTHING. A deployment that chose Shared keeps it.
+UPDATE "mcp_servers"
+ SET "account_mode" = 'personal'
+ WHERE "provenance" IN ('composio', 'arcade')
+ AND "account_mode" IS NULL
+ AND ("auth_scheme" IS NULL OR "auth_scheme" <> 'NO_AUTH');
diff --git a/server/drizzle/0062_pending_authorization_user_is_actor.sql b/server/drizzle/0062_pending_authorization_user_is_actor.sql
new file mode 100644
index 000000000..a682698b6
--- /dev/null
+++ b/server/drizzle/0062_pending_authorization_user_is_actor.sql
@@ -0,0 +1,21 @@
+-- An attempt belongs to the person who started it, and only that person's deletion may take it.
+--
+-- `0060` keyed both `actor_id` and `user_id` to `users` and cascaded both. `actor_id` is the row's
+-- owner (the primary key, the session subject the verifier matches). `user_id` is whose account the
+-- attempt is for, which for a `person` attempt is always the actor — `accountRefFor` answers a
+-- Personal app with `{ userId: actorId }` and the only writer copies that — but nothing in the
+-- database said so. A row naming two people therefore died with EITHER of them: deleting one person
+-- destroyed an attempt a DIFFERENT person had started and was consenting through.
+--
+-- So the equality becomes a CHECK, and `user_id` loses its own key and the index that served it.
+-- Through the CHECK, `user_id` names a person exactly when `actor_id` does, and `actor_id`'s cascade
+-- (B7's reasoning, unchanged) is the only one left that reaches this table from `users`.
+--
+-- NO PURGE BEFORE THE CHECK, unlike 0060's orphans. Every row the application writes already holds
+-- the equality, so a violating row was written by hand; deleting it would be the very loss this
+-- migration exists to stop. If one exists, the ADD CONSTRAINT refuses and names the constraint, and
+-- an operator decides what that row was.
+ALTER TABLE "pending_authorizations" DROP CONSTRAINT "pending_authorizations_user_id_users_id_fk";
+--> statement-breakpoint
+DROP INDEX "pending_authorizations_user_idx";--> statement-breakpoint
+ALTER TABLE "pending_authorizations" ADD CONSTRAINT "pending_authorizations_user_is_actor_check" CHECK ("pending_authorizations"."user_id" IS NULL OR "pending_authorizations"."user_id" = "pending_authorizations"."actor_id");
diff --git a/server/drizzle/0063_mcp_tool_vendor_name.sql b/server/drizzle/0063_mcp_tool_vendor_name.sql
new file mode 100644
index 000000000..eeaa02b1c
--- /dev/null
+++ b/server/drizzle/0063_mcp_tool_vendor_name.sql
@@ -0,0 +1,36 @@
+-- An action's own name and the name a model may call it by are two facts, and `mcp_tools` held one.
+--
+-- `name` is this deployment's identifier: half of the `/` ref a grant is written
+-- against, and the half of `mcp____` a model is offered. The model API accepts a
+-- tool name matching `^[a-zA-Z0-9_-]+$` and nothing else.
+--
+-- Arcade's listing answers `qualified_name` — `Gmail.ListEmails` — and the dot is legal there and
+-- illegal in a tool name. With one column that spelling went straight into the offered name, and
+-- because a request carries every tool together, ONE granted Arcade action failed the WHOLE model
+-- call with `Invalid 'tools[47].name'`. No Arcade action had ever been callable by any model.
+--
+-- So the vendor's spelling gets its own column and `name` becomes offerable by construction.
+--
+-- ADDED WITH A DEFAULT, BACKFILLED, THEN STRIPPED OF IT, rather than the bare `ADD COLUMN ... NOT
+-- NULL` the generator writes. That form is only correct against an EMPTY table: every deployment
+-- holding one recorded action would have the statement refused and the migration stop. The default
+-- is scaffolding and not a value — Postgres cannot default a column to another column, so the empty
+-- string stands only for the instant between the ADD and the UPDATE below, and `DROP DEFAULT` is
+-- what keeps it from becoming the answer for some later insert that forgets this column.
+--
+-- BACKFILLED TO `name`, WHICH IS CORRECT AND NOT A PLACEHOLDER. Every row that exists when this runs
+-- was listed by Composio, an MCP server or the Drive REST transport, and all three publish names
+-- that are already offerable — the only non-alphanumeric character in any tool name this
+-- deployment has recorded is `_`. For those transports the two spellings are genuinely the same
+-- string, which is what the backfill records.
+--
+-- ARCADE ROWS ARE NOT MIGRATED, THEY ARE RE-LISTED. Their `name` has to LOSE the toolkit prefix, and
+-- rewriting it here would leave every grant, skill ref and approval snapshot pointing at a ref that
+-- no longer exists — a silent re-pointing of authorization records, which is the one thing a
+-- migration must not do by inference. The listing is the authority on what an app publishes, so an
+-- administrator presses Refresh and the rows are rebuilt from the vendor with both names recorded.
+-- Nothing is lost that the vendor cannot answer again, and no Arcade grant was callable before this
+-- commit anyway: there is no working state to preserve.
+ALTER TABLE "mcp_tools" ADD COLUMN "vendor_name" text DEFAULT '' NOT NULL;--> statement-breakpoint
+UPDATE "mcp_tools" SET "vendor_name" = "name";--> statement-breakpoint
+ALTER TABLE "mcp_tools" ALTER COLUMN "vendor_name" DROP DEFAULT;
diff --git a/server/drizzle/meta/0057_snapshot.json b/server/drizzle/meta/0057_snapshot.json
new file mode 100644
index 000000000..be2d28b29
--- /dev/null
+++ b/server/drizzle/meta/0057_snapshot.json
@@ -0,0 +1,9951 @@
+{
+ "id": "529de577-9b19-43d1-9885-dd32ddae9bd6",
+ "prevId": "1e95338a-aa54-4146-9f37-32111c369e75",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0058_snapshot.json b/server/drizzle/meta/0058_snapshot.json
new file mode 100644
index 000000000..41e065ff1
--- /dev/null
+++ b/server/drizzle/meta/0058_snapshot.json
@@ -0,0 +1,9960 @@
+{
+ "id": "d7748e18-de30-4092-afcd-21350f0829a1",
+ "prevId": "529de577-9b19-43d1-9885-dd32ddae9bd6",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0059_snapshot.json b/server/drizzle/meta/0059_snapshot.json
new file mode 100644
index 000000000..9f947ad31
--- /dev/null
+++ b/server/drizzle/meta/0059_snapshot.json
@@ -0,0 +1,9964 @@
+{
+ "id": "a1b20dec-1a95-4bb3-b4b1-d1196a22a6cf",
+ "prevId": "d7748e18-de30-4092-afcd-21350f0829a1",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ },
+ "pending_authorizations_completion_check": {
+ "name": "pending_authorizations_completion_check",
+ "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0060_snapshot.json b/server/drizzle/meta/0060_snapshot.json
new file mode 100644
index 000000000..e45a97bda
--- /dev/null
+++ b/server/drizzle/meta/0060_snapshot.json
@@ -0,0 +1,10021 @@
+{
+ "id": "4dd738d3-9934-4fe3-adf8-725b8b8439ee",
+ "prevId": "a1b20dec-1a95-4bb3-b4b1-d1196a22a6cf",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "pending_authorizations_server_idx": {
+ "name": "pending_authorizations_server_idx",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "pending_authorizations_user_idx": {
+ "name": "pending_authorizations_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_authorizations_actor_id_users_id_fk": {
+ "name": "pending_authorizations_actor_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_user_id_users_id_fk": {
+ "name": "pending_authorizations_user_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ },
+ "pending_authorizations_completion_check": {
+ "name": "pending_authorizations_completion_check",
+ "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0061_snapshot.json b/server/drizzle/meta/0061_snapshot.json
new file mode 100644
index 000000000..a1b63c6a0
--- /dev/null
+++ b/server/drizzle/meta/0061_snapshot.json
@@ -0,0 +1,10021 @@
+{
+ "id": "bc785e20-49f3-4886-b0b1-a81186dcbe0c",
+ "prevId": "4dd738d3-9934-4fe3-adf8-725b8b8439ee",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "pending_authorizations_server_idx": {
+ "name": "pending_authorizations_server_idx",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "pending_authorizations_user_idx": {
+ "name": "pending_authorizations_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_authorizations_actor_id_users_id_fk": {
+ "name": "pending_authorizations_actor_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_user_id_users_id_fk": {
+ "name": "pending_authorizations_user_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ },
+ "pending_authorizations_completion_check": {
+ "name": "pending_authorizations_completion_check",
+ "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0062_snapshot.json b/server/drizzle/meta/0062_snapshot.json
new file mode 100644
index 000000000..82e9e0759
--- /dev/null
+++ b/server/drizzle/meta/0062_snapshot.json
@@ -0,0 +1,9997 @@
+{
+ "id": "715bfd83-d33c-4c64-8233-060458d32510",
+ "prevId": "bc785e20-49f3-4886-b0b1-a81186dcbe0c",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "pending_authorizations_server_idx": {
+ "name": "pending_authorizations_server_idx",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_authorizations_actor_id_users_id_fk": {
+ "name": "pending_authorizations_actor_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ },
+ "pending_authorizations_completion_check": {
+ "name": "pending_authorizations_completion_check",
+ "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)"
+ },
+ "pending_authorizations_user_is_actor_check": {
+ "name": "pending_authorizations_user_is_actor_check",
+ "value": "\"pending_authorizations\".\"user_id\" IS NULL OR \"pending_authorizations\".\"user_id\" = \"pending_authorizations\".\"actor_id\""
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/0063_snapshot.json b/server/drizzle/meta/0063_snapshot.json
new file mode 100644
index 000000000..f2311f2b5
--- /dev/null
+++ b/server/drizzle/meta/0063_snapshot.json
@@ -0,0 +1,10003 @@
+{
+ "id": "645222d2-0cbd-418f-a0d7-72628d49059c",
+ "prevId": "715bfd83-d33c-4c64-8233-060458d32510",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "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
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounts_provider_account_idx": {
+ "name": "accounts_provider_account_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agents": {
+ "name": "agents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "agent_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "configuration": {
+ "name": "configuration",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agents_package_id_deployment_packages_id_fk": {
+ "name": "agents_package_id_deployment_packages_id_fk",
+ "tableFrom": "agents",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.attachments": {
+ "name": "attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bytes": {
+ "name": "bytes",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "attached_at": {
+ "name": "attached_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "upload_group": {
+ "name": "upload_group",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "attachments_channel_idx": {
+ "name": "attachments_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_uploaded_by_idx": {
+ "name": "attachments_uploaded_by_idx",
+ "columns": [
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_upload_group_idx": {
+ "name": "attachments_upload_group_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "upload_group",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "attachments_staged_idx": {
+ "name": "attachments_staged_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"attachments\".\"attached_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "attachments_channel_id_channels_id_fk": {
+ "name": "attachments_channel_id_channels_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "attachments_uploaded_by_users_id_fk": {
+ "name": "attachments_uploaded_by_users_id_fk",
+ "tableFrom": "attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_events": {
+ "name": "audit_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "initiator_kind": {
+ "name": "initiator_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "initiator_id": {
+ "name": "initiator_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_type": {
+ "name": "target_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_events_created_at_idx": {
+ "name": "audit_events_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_type_time_idx": {
+ "name": "audit_events_type_time_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_actor_time_idx": {
+ "name": "audit_events_actor_time_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_target_time_idx": {
+ "name": "audit_events_target_time_idx",
+ "columns": [
+ {
+ "expression": "target_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_events_initiator_time_idx": {
+ "name": "audit_events_initiator_time_idx",
+ "columns": [
+ {
+ "expression": "initiator_kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_agents": {
+ "name": "channel_agents",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_agents_channel_id_channels_id_fk": {
+ "name": "channel_agents_channel_id_channels_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_agents_agent_id_agents_id_fk": {
+ "name": "channel_agents_agent_id_agents_id_fk",
+ "tableFrom": "channel_agents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_agents_channel_id_agent_id_pk": {
+ "name": "channel_agents_channel_id_agent_id_pk",
+ "columns": [
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channel_memberships": {
+ "name": "channel_memberships",
+ "schema": "",
+ "columns": {
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_read_at": {
+ "name": "last_read_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "channel_memberships_channel_id_channels_id_fk": {
+ "name": "channel_memberships_channel_id_channels_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "channel_memberships_user_id_users_id_fk": {
+ "name": "channel_memberships_user_id_users_id_fk",
+ "tableFrom": "channel_memberships",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "channel_memberships_channel_id_user_id_pk": {
+ "name": "channel_memberships_channel_id_user_id_pk",
+ "columns": [
+ "channel_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.channels": {
+ "name": "channels",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "suggested_prompts": {
+ "name": "suggested_prompts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "allowed_groups": {
+ "name": "allowed_groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "package_id": {
+ "name": "package_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "override": {
+ "name": "override",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_at": {
+ "name": "summary_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message": {
+ "name": "last_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_source_id": {
+ "name": "last_message_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_at": {
+ "name": "last_message_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_message_agent_id": {
+ "name": "last_message_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "channels_recent_activity_idx": {
+ "name": "channels_recent_activity_idx",
+ "columns": [
+ {
+ "expression": "COALESCE(\"last_message_at\", \"created_at\") DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "channels_awaiting_summary_idx": {
+ "name": "channels_awaiting_summary_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"channels\".\"summary\" is null and \"channels\".\"deleted_at\" is null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "channels_package_id_deployment_packages_id_fk": {
+ "name": "channels_package_id_deployment_packages_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "deployment_packages",
+ "columnsFrom": [
+ "package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "channels_last_message_agent_id_agents_id_fk": {
+ "name": "channels_last_message_agent_id_agents_id_fk",
+ "tableFrom": "channels",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "last_message_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credentials": {
+ "name": "credentials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "credential_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_value": {
+ "name": "encrypted_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key_id": {
+ "name": "key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credentials_active_key_idx": {
+ "name": "credentials_active_key_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credentials\".\"revoked_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment_packages": {
+ "name": "deployment_packages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_path": {
+ "name": "source_path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checksum": {
+ "name": "checksum",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "loaded_at": {
+ "name": "loaded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployment_packages_tenant_id_unique": {
+ "name": "deployment_packages_tenant_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "tenant_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.intelligence_channel_mappings": {
+ "name": "intelligence_channel_mappings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "intelligence_channel_mappings_thread_idx": {
+ "name": "intelligence_channel_mappings_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "intelligence_channel_mappings_user_id_users_id_fk": {
+ "name": "intelligence_channel_mappings_user_id_users_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "intelligence_channel_mappings_channel_id_channels_id_fk": {
+ "name": "intelligence_channel_mappings_channel_id_channels_id_fk",
+ "tableFrom": "intelligence_channel_mappings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "intelligence_channel_mappings_user_id_channel_id_pk": {
+ "name": "intelligence_channel_mappings_user_id_channel_id_pk",
+ "columns": [
+ "user_id",
+ "channel_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.revoked_access": {
+ "name": "revoked_access",
+ "schema": "",
+ "columns": {
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_by": {
+ "name": "revoked_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_providers": {
+ "name": "sso_providers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "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": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sso_providers_user_id_users_id_fk": {
+ "name": "sso_providers_user_id_users_id_fk",
+ "tableFrom": "sso_providers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sso_providers_provider_id_unique": {
+ "name": "sso_providers_provider_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "provider_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_instructions": {
+ "name": "user_instructions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_instructions_user_id_users_id_fk": {
+ "name": "user_instructions_user_id_users_id_fk",
+ "tableFrom": "user_instructions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_roles": {
+ "name": "user_roles",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_roles_user_id_users_id_fk": {
+ "name": "user_roles_user_id_users_id_fk",
+ "tableFrom": "user_roles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_roles_user_id_role_pk": {
+ "name": "user_roles_user_id_role_pk",
+ "columns": [
+ "user_id",
+ "role"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "groups": {
+ "name": "groups",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "onboarding_step": {
+ "name": "onboarding_step",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_completed_at": {
+ "name": "onboarding_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_signed_in_at": {
+ "name": "last_signed_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.action_policy": {
+ "name": "action_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deny": {
+ "name": "deny",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allow": {
+ "name": "allow",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_page_frame": {
+ "name": "computer_page_frame",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame": {
+ "name": "frame",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "captured_at": {
+ "name": "captured_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "computer_page_frame_captured_idx": {
+ "name": "computer_page_frame_captured_idx",
+ "columns": [
+ {
+ "expression": "captured_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "computer_page_frame_computer_id_tool_call_id_pk": {
+ "name": "computer_page_frame_computer_id_tool_call_id_pk",
+ "columns": [
+ "computer_id",
+ "tool_call_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.computer_snapshot": {
+ "name": "computer_snapshot",
+ "schema": "",
+ "columns": {
+ "computer_id": {
+ "name": "computer_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "snapshot_id": {
+ "name": "snapshot_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elements": {
+ "name": "elements",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "taken_at": {
+ "name": "taken_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "session": {
+ "name": "session",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_preferences": {
+ "name": "agent_preferences",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hidden_at": {
+ "name": "hidden_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "agent_preferences_user_id_users_id_fk": {
+ "name": "agent_preferences_user_id_users_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_preferences_agent_id_agents_id_fk": {
+ "name": "agent_preferences_agent_id_agents_id_fk",
+ "tableFrom": "agent_preferences",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "agent_preferences_user_id_agent_id_pk": {
+ "name": "agent_preferences_user_id_agent_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.agent_profiles": {
+ "name": "agent_profiles",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_description": {
+ "name": "role_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_seed": {
+ "name": "avatar_seed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "agent_visibility",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "callback_token_hash": {
+ "name": "callback_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "callback_token_issued_at": {
+ "name": "callback_token_issued_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "agent_profiles_visibility_deleted_idx": {
+ "name": "agent_profiles_visibility_deleted_idx",
+ "columns": [
+ {
+ "expression": "visibility",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "agent_profiles_agent_id_agents_id_fk": {
+ "name": "agent_profiles_agent_id_agents_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "agent_profiles_owner_user_id_users_id_fk": {
+ "name": "agent_profiles_owner_user_id_users_id_fk",
+ "tableFrom": "agent_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_runs": {
+ "name": "routine_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "routine_id": {
+ "name": "routine_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "routine_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'schedule'"
+ }
+ },
+ "indexes": {
+ "routine_runs_by_routine_idx": {
+ "name": "routine_runs_by_routine_idx",
+ "columns": [
+ {
+ "expression": "routine_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routine_runs_routine_id_routines_id_fk": {
+ "name": "routine_runs_routine_id_routines_id_fk",
+ "tableFrom": "routine_runs",
+ "tableTo": "routines",
+ "columnsFrom": [
+ "routine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routine_sweeps": {
+ "name": "routine_sweeps",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "swept_at": {
+ "name": "swept_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.routines": {
+ "name": "routines",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron": {
+ "name": "cron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled_at": {
+ "name": "enabled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "routines_due_idx": {
+ "name": "routines_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "routines_by_owner_idx": {
+ "name": "routines_by_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "routines_owner_user_id_users_id_fk": {
+ "name": "routines_owner_user_id_users_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "routines_agent_id_agents_id_fk": {
+ "name": "routines_agent_id_agents_id_fk",
+ "tableFrom": "routines",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_exclusions": {
+ "name": "component_exclusions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "withheld_by": {
+ "name": "withheld_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_exclusions_component_name_components_name_fk": {
+ "name": "component_exclusions_component_name_components_name_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "component_exclusions_agent_id_agents_id_fk": {
+ "name": "component_exclusions_agent_id_agents_id_fk",
+ "tableFrom": "component_exclusions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_exclusions_component_name_agent_id_pk": {
+ "name": "component_exclusions_component_name_agent_id_pk",
+ "columns": [
+ "component_name",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.component_functions": {
+ "name": "component_functions",
+ "schema": "",
+ "columns": {
+ "component_name": {
+ "name": "component_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "function_name": {
+ "name": "function_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "component_functions_component_name_components_name_fk": {
+ "name": "component_functions_component_name_components_name_fk",
+ "tableFrom": "component_functions",
+ "tableTo": "components",
+ "columnsFrom": [
+ "component_name"
+ ],
+ "columnsTo": [
+ "name"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "component_functions_component_name_function_name_pk": {
+ "name": "component_functions_component_name_function_name_pk",
+ "columns": [
+ "component_name",
+ "function_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.components": {
+ "name": "components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.brokered_connections": {
+ "name": "brokered_connections",
+ "schema": "",
+ "columns": {
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "app": {
+ "name": "app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "brokered_connections_person_idx": {
+ "name": "brokered_connections_person_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'person'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_deployment_idx": {
+ "name": "brokered_connections_deployment_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "app",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"brokered_connections\".\"holder\" = 'deployment'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "brokered_connections_user_idx": {
+ "name": "brokered_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "brokered_connections_holder_check": {
+ "name": "brokered_connections_holder_check",
+ "value": "(\"brokered_connections\".\"holder\" = 'person' AND \"brokered_connections\".\"user_id\" IS NOT NULL) OR (\"brokered_connections\".\"holder\" = 'deployment' AND \"brokered_connections\".\"user_id\" IS NULL)"
+ },
+ "brokered_connections_provider_check": {
+ "name": "brokered_connections_provider_check",
+ "value": "\"brokered_connections\".\"provider\" IN ('composio', 'arcade')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.composio_connections": {
+ "name": "composio_connections",
+ "schema": "",
+ "columns": {
+ "toolkit": {
+ "name": "toolkit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "verified": {
+ "name": "verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "probe_action": {
+ "name": "probe_action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "composio_connections_user_idx": {
+ "name": "composio_connections_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "composio_connections_toolkit_user_id_pk": {
+ "name": "composio_connections_toolkit_user_id_pk",
+ "columns": [
+ "toolkit",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'first-party'"
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_scheme": {
+ "name": "auth_scheme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_mode": {
+ "name": "account_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shared_vendor_user_id": {
+ "name": "shared_vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tools_refreshed_at": {
+ "name": "tools_refreshed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_servers_credential_id_credentials_id_fk": {
+ "name": "mcp_servers_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "mcp_servers_account_mode_check": {
+ "name": "mcp_servers_account_mode_check",
+ "value": "\"mcp_servers\".\"account_mode\" IS NULL OR \"mcp_servers\".\"account_mode\" IN ('personal', 'shared')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mcp_tools": {
+ "name": "mcp_tools",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "vendor_name": {
+ "name": "vendor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destructive": {
+ "name": "destructive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "version": {
+ "name": "version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mcp_tools_server_id_mcp_servers_id_fk": {
+ "name": "mcp_tools_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_tools",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_tools_server_id_name_pk": {
+ "name": "mcp_tools_server_id_name_pk",
+ "columns": [
+ "server_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_user_credentials": {
+ "name": "mcp_user_credentials",
+ "schema": "",
+ "columns": {
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_user_credentials_user_idx": {
+ "name": "mcp_user_credentials_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_user_credentials_server_id_mcp_servers_id_fk": {
+ "name": "mcp_user_credentials_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_user_id_users_id_fk": {
+ "name": "mcp_user_credentials_user_id_users_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_user_credentials_credential_id_credentials_id_fk": {
+ "name": "mcp_user_credentials_credential_id_credentials_id_fk",
+ "tableFrom": "mcp_user_credentials",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "mcp_user_credentials_server_id_user_id_pk": {
+ "name": "mcp_user_credentials_server_id_user_id_pk",
+ "columns": [
+ "server_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_authorizations": {
+ "name": "pending_authorizations",
+ "schema": "",
+ "columns": {
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "holder": {
+ "name": "holder",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor_user_id": {
+ "name": "vendor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth_id": {
+ "name": "auth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "pending_authorizations_server_idx": {
+ "name": "pending_authorizations_server_idx",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_authorizations_actor_id_users_id_fk": {
+ "name": "pending_authorizations_actor_id_users_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "actor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_authorizations_server_id_mcp_servers_id_fk": {
+ "name": "pending_authorizations_server_id_mcp_servers_id_fk",
+ "tableFrom": "pending_authorizations",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "pending_authorizations_holder_check": {
+ "name": "pending_authorizations_holder_check",
+ "value": "(\"pending_authorizations\".\"holder\" = 'person' AND \"pending_authorizations\".\"user_id\" IS NOT NULL) OR (\"pending_authorizations\".\"holder\" = 'deployment' AND \"pending_authorizations\".\"user_id\" IS NULL)"
+ },
+ "pending_authorizations_outcome_check": {
+ "name": "pending_authorizations_outcome_check",
+ "value": "\"pending_authorizations\".\"outcome\" IS NULL OR \"pending_authorizations\".\"outcome\" IN ('completed', 'failed', 'unreconciled', 'withdrawn')"
+ },
+ "pending_authorizations_completion_check": {
+ "name": "pending_authorizations_completion_check",
+ "value": "(\"pending_authorizations\".\"completed_at\" IS NULL) = (\"pending_authorizations\".\"outcome\" IS NULL)"
+ },
+ "pending_authorizations_user_is_actor_check": {
+ "name": "pending_authorizations_user_is_actor_check",
+ "value": "\"pending_authorizations\".\"user_id\" IS NULL OR \"pending_authorizations\".\"user_id\" = \"pending_authorizations\".\"actor_id\""
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.plugin_grants": {
+ "name": "plugin_grants",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granted_by": {
+ "name": "granted_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "plugin_grants_agent_idx": {
+ "name": "plugin_grants_agent_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "plugin_grants_agent_id_agents_id_fk": {
+ "name": "plugin_grants_agent_id_agents_id_fk",
+ "tableFrom": "plugin_grants",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "plugin_grants_kind_ref_agent_id_pk": {
+ "name": "plugin_grants_kind_ref_agent_id_pk",
+ "columns": [
+ "kind",
+ "ref",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandboxed_components": {
+ "name": "sandboxed_components",
+ "schema": "",
+ "columns": {
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_description": {
+ "name": "draft_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_html": {
+ "name": "draft_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_css": {
+ "name": "draft_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_js_functions": {
+ "name": "draft_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "draft_argument_schema": {
+ "name": "draft_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "published_description": {
+ "name": "published_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_html": {
+ "name": "published_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_css": {
+ "name": "published_css",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_js_functions": {
+ "name": "published_js_functions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "published_argument_schema": {
+ "name": "published_argument_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sample_arguments": {
+ "name": "sample_arguments",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "published": {
+ "name": "published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authored_by": {
+ "name": "authored_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_tools": {
+ "name": "skill_tools",
+ "schema": "",
+ "columns": {
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ref": {
+ "name": "ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "declared_by": {
+ "name": "declared_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_tools_ref_idx": {
+ "name": "skill_tools_ref_idx",
+ "columns": [
+ {
+ "expression": "ref",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_tools_skill_id_skills_id_fk": {
+ "name": "skill_tools_skill_id_skills_id_fk",
+ "tableFrom": "skill_tools",
+ "tableTo": "skills",
+ "columnsFrom": [
+ "skill_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "skill_tools_skill_id_ref_pk": {
+ "name": "skill_tools_skill_id_ref_pk",
+ "columns": [
+ "skill_id",
+ "ref"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skills": {
+ "name": "skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instructions": {
+ "name": "instructions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'yours'"
+ },
+ "installed_by": {
+ "name": "installed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skills_slug_key": {
+ "name": "skills_slug_key",
+ "columns": [
+ {
+ "expression": "slug",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skills_owner_idx": {
+ "name": "skills_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skills_owner_user_id_users_id_fk": {
+ "name": "skills_owner_user_id_users_id_fk",
+ "tableFrom": "skills",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.work_items": {
+ "name": "work_items",
+ "schema": "",
+ "columns": {
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_at": {
+ "name": "run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lease_until": {
+ "name": "lease_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "work_items_claimable_idx": {
+ "name": "work_items_claimable_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "work_items_kind_key_pk": {
+ "name": "work_items_kind_key_pk",
+ "columns": [
+ "kind",
+ "key"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.voice_sessions": {
+ "name": "voice_sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "anchor_message_id": {
+ "name": "anchor_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_seconds": {
+ "name": "duration_seconds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transcript": {
+ "name": "transcript",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "summary_status": {
+ "name": "summary_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'failed'"
+ }
+ },
+ "indexes": {
+ "voice_sessions_channel_started_idx": {
+ "name": "voice_sessions_channel_started_idx",
+ "columns": [
+ {
+ "expression": "channel_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "voice_sessions_channel_id_channels_id_fk": {
+ "name": "voice_sessions_channel_id_channels_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "voice_sessions_user_id_users_id_fk": {
+ "name": "voice_sessions_user_id_users_id_fk",
+ "tableFrom": "voice_sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "voice_sessions_duration_check": {
+ "name": "voice_sessions_duration_check",
+ "value": "\"voice_sessions\".\"duration_seconds\" >= 0"
+ },
+ "voice_sessions_summary_check": {
+ "name": "voice_sessions_summary_check",
+ "value": "(\"voice_sessions\".\"summary_status\" = 'ready' AND \"voice_sessions\".\"summary\" IS NOT NULL) OR (\"voice_sessions\".\"summary_status\" = 'failed' AND \"voice_sessions\".\"summary\" IS NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.learning_settings": {
+ "name": "learning_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "settings": {
+ "name": "settings",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.learning_thread_bindings": {
+ "name": "learning_thread_bindings",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "container_id": {
+ "name": "container_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "learning_thread_bindings_user_id_thread_id_pk": {
+ "name": "learning_thread_bindings_user_id_thread_id_pk",
+ "columns": [
+ "user_id",
+ "thread_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibilities": {
+ "name": "responsibilities",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "instruction": {
+ "name": "instruction",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "success_criteria": {
+ "name": "success_criteria",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "progress": {
+ "name": "progress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_result": {
+ "name": "last_result",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subscriptions": {
+ "name": "subscriptions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibilities_owner_status_idx": {
+ "name": "responsibilities_owner_status_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibilities_owner_user_id_users_id_fk": {
+ "name": "responsibilities_owner_user_id_users_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibilities_agent_id_agents_id_fk": {
+ "name": "responsibilities_agent_id_agents_id_fk",
+ "tableFrom": "responsibilities",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_events": {
+ "name": "responsibility_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_events_source_id_idx": {
+ "name": "responsibility_events_source_id_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_events_owner_user_id_users_id_fk": {
+ "name": "responsibility_events_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_runs": {
+ "name": "responsibility_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "responsibility_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "reply_text": {
+ "name": "reply_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waiting": {
+ "name": "waiting",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_runs_event_idx": {
+ "name": "responsibility_runs_event_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_runs_status_idx": {
+ "name": "responsibility_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_runs_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_runs_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_runs_event_id_responsibility_events_id_fk": {
+ "name": "responsibility_runs_event_id_responsibility_events_id_fk",
+ "tableFrom": "responsibility_runs",
+ "tableTo": "responsibility_events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_source_bindings": {
+ "name": "responsibility_source_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_source_bindings_owner_idx": {
+ "name": "responsibility_source_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_source_bindings_owner_user_id_users_id_fk": {
+ "name": "responsibility_source_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_source_bindings_credential_id_credentials_id_fk": {
+ "name": "responsibility_source_bindings_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_source_bindings",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.responsibility_triggers": {
+ "name": "responsibility_triggers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "responsibility_id": {
+ "name": "responsibility_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "responsibility_triggers_owner_idx": {
+ "name": "responsibility_triggers_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_responsibility_idx": {
+ "name": "responsibility_triggers_responsibility_idx",
+ "columns": [
+ {
+ "expression": "responsibility_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "responsibility_triggers_kind_idx": {
+ "name": "responsibility_triggers_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "responsibility_triggers_owner_user_id_users_id_fk": {
+ "name": "responsibility_triggers_owner_user_id_users_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_responsibility_id_responsibilities_id_fk": {
+ "name": "responsibility_triggers_responsibility_id_responsibilities_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "responsibilities",
+ "columnsFrom": [
+ "responsibility_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "responsibility_triggers_credential_id_credentials_id_fk": {
+ "name": "responsibility_triggers_credential_id_credentials_id_fk",
+ "tableFrom": "responsibility_triggers",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.approval_preferences": {
+ "name": "approval_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auto_review": {
+ "name": "auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host_commands": {
+ "name": "host_commands",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ask'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_preferences_owner_user_id_users_id_fk": {
+ "name": "approval_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "approval_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_preferences_host_commands_check": {
+ "name": "approval_preferences_host_commands_check",
+ "value": "\"approval_preferences\".\"host_commands\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_requests": {
+ "name": "approval_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action_digest": {
+ "name": "action_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "approval_requests_action_idx": {
+ "name": "approval_requests_action_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "approval_requests_inbox_idx": {
+ "name": "approval_requests_inbox_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_requests_owner_user_id_users_id_fk": {
+ "name": "approval_requests_owner_user_id_users_id_fk",
+ "tableFrom": "approval_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_status_check": {
+ "name": "approval_status_check",
+ "value": "\"approval_requests\".\"status\" IN ('pending', 'approved', 'denied', 'consumed', 'completed')"
+ },
+ "approval_decision_check": {
+ "name": "approval_decision_check",
+ "value": "\"approval_requests\".\"decision\" IN ('allow_once', 'allow_always', 'deny', 'handled')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_rules": {
+ "name": "approval_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "approval_rules_owner_idx": {
+ "name": "approval_rules_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "approval_rules_owner_user_id_users_id_fk": {
+ "name": "approval_rules_owner_user_id_users_id_fk",
+ "tableFrom": "approval_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_rules_behaviour_check": {
+ "name": "approval_rules_behaviour_check",
+ "value": "\"approval_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_rules": {
+ "name": "approval_team_rules",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effect": {
+ "name": "effect",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "behaviour": {
+ "name": "behaviour",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_rules_created_by_users_id_fk": {
+ "name": "approval_team_rules_created_by_users_id_fk",
+ "tableFrom": "approval_team_rules",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_rules_behaviour_check": {
+ "name": "approval_team_rules_behaviour_check",
+ "value": "\"approval_team_rules\".\"behaviour\" IN ('allow', 'pre_approved', 'ask', 'hand_off')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.approval_team_settings": {
+ "name": "approval_team_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "enforce_auto_review": {
+ "name": "enforce_auto_review",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "custom_rules_enabled": {
+ "name": "custom_rules_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "host_commands_cap": {
+ "name": "host_commands_cap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'allow'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "approval_team_settings_updated_by_users_id_fk": {
+ "name": "approval_team_settings_updated_by_users_id_fk",
+ "tableFrom": "approval_team_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "approval_team_settings_host_commands_cap_check": {
+ "name": "approval_team_settings_host_commands_cap_check",
+ "value": "\"approval_team_settings\".\"host_commands_cap\" IN ('ask', 'allow', 'never')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.memory_sources": {
+ "name": "memory_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_ref": {
+ "name": "tool_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sync_status": {
+ "name": "sync_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "sync_error": {
+ "name": "sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "memory_sources_owner_idx": {
+ "name": "memory_sources_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_sources_due_idx": {
+ "name": "memory_sources_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_sync_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_sources_owner_user_id_users_id_fk": {
+ "name": "memory_sources_owner_user_id_users_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "memory_sources_agent_id_agents_id_fk": {
+ "name": "memory_sources_agent_id_agents_id_fk",
+ "tableFrom": "memory_sources",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.personal_memories": {
+ "name": "personal_memories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "import_digest": {
+ "name": "import_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provenance": {
+ "name": "provenance",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "review_state": {
+ "name": "review_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unreviewed'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "formed_by": {
+ "name": "formed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'person'"
+ },
+ "formed_by_agent_id": {
+ "name": "formed_by_agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "observed_at": {
+ "name": "observed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_memory_id": {
+ "name": "intelligence_memory_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "intelligence_digest": {
+ "name": "intelligence_digest",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "personal_memories_owner_idx": {
+ "name": "personal_memories_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_intelligence_idx": {
+ "name": "personal_memories_intelligence_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "intelligence_memory_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_idx": {
+ "name": "personal_memories_formed_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "formed_by_agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_import_idx": {
+ "name": "personal_memories_import_idx",
+ "columns": [
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "personal_memories_formed_digest_idx": {
+ "name": "personal_memories_formed_digest_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "import_digest",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"personal_memories\".\"formed_by\" = 'bot'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "personal_memories_owner_user_id_users_id_fk": {
+ "name": "personal_memories_owner_user_id_users_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_source_id_memory_sources_id_fk": {
+ "name": "personal_memories_source_id_memory_sources_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "memory_sources",
+ "columnsFrom": [
+ "source_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "personal_memories_formed_by_agent_id_agents_id_fk": {
+ "name": "personal_memories_formed_by_agent_id_agents_id_fk",
+ "tableFrom": "personal_memories",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "formed_by_agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.default_opt_outs": {
+ "name": "default_opt_outs",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feature": {
+ "name": "feature",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "default_opt_outs_owner_user_id_users_id_fk": {
+ "name": "default_opt_outs_owner_user_id_users_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "default_opt_outs_agent_id_agents_id_fk": {
+ "name": "default_opt_outs_agent_id_agents_id_fk",
+ "tableFrom": "default_opt_outs",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "default_opt_outs_owner_user_id_agent_id_feature_scope_pk": {
+ "name": "default_opt_outs_owner_user_id_agent_id_feature_scope_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id",
+ "feature",
+ "scope"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "default_opt_outs_feature_check": {
+ "name": "default_opt_outs_feature_check",
+ "value": "\"default_opt_outs\".\"feature\" IN ('memory_source', 'proactive_research')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_settings": {
+ "name": "proactive_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "focus": {
+ "name": "focus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "interval_minutes": {
+ "name": "interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 240
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_status": {
+ "name": "last_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_settings_owner_agent_idx": {
+ "name": "proactive_settings_owner_agent_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "proactive_settings_due_idx": {
+ "name": "proactive_settings_due_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_settings_owner_user_id_users_id_fk": {
+ "name": "proactive_settings_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_settings_agent_id_agents_id_fk": {
+ "name": "proactive_settings_agent_id_agents_id_fk",
+ "tableFrom": "proactive_settings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_settings_interval_check": {
+ "name": "proactive_settings_interval_check",
+ "value": "\"proactive_settings\".\"interval_minutes\" >= 60 AND \"proactive_settings\".\"interval_minutes\" <= 1440"
+ },
+ "proactive_settings_status_check": {
+ "name": "proactive_settings_status_check",
+ "value": "\"proactive_settings\".\"last_status\" IN ('idle', 'running', 'succeeded', 'error')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.proactive_suggestions": {
+ "name": "proactive_suggestions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "setting_id": {
+ "name": "setting_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "detail": {
+ "name": "detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_app": {
+ "name": "source_app",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_ref": {
+ "name": "source_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "delivered_at": {
+ "name": "delivered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "proactive_suggestions_owner_idx": {
+ "name": "proactive_suggestions_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_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": {}
+ },
+ "proactive_suggestions_run_idx": {
+ "name": "proactive_suggestions_run_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "proactive_suggestions_owner_user_id_users_id_fk": {
+ "name": "proactive_suggestions_owner_user_id_users_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_agent_id_agents_id_fk": {
+ "name": "proactive_suggestions_agent_id_agents_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "proactive_suggestions_setting_id_proactive_settings_id_fk": {
+ "name": "proactive_suggestions_setting_id_proactive_settings_id_fk",
+ "tableFrom": "proactive_suggestions",
+ "tableTo": "proactive_settings",
+ "columnsFrom": [
+ "setting_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "proactive_suggestions_status_check": {
+ "name": "proactive_suggestions_status_check",
+ "value": "\"proactive_suggestions\".\"status\" IN ('open', 'dismissed', 'started')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.demonstrations": {
+ "name": "demonstrations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'recording'"
+ },
+ "actions": {
+ "name": "actions",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "draft": {
+ "name": "draft",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "skill_slug": {
+ "name": "skill_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "finished_at": {
+ "name": "finished_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "demonstrations_owner_idx": {
+ "name": "demonstrations_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "demonstrations_active_bot_idx": {
+ "name": "demonstrations_active_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"demonstrations\".\"status\" = 'recording'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "demonstrations_owner_user_id_users_id_fk": {
+ "name": "demonstrations_owner_user_id_users_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "demonstrations_bot_id_agents_id_fk": {
+ "name": "demonstrations_bot_id_agents_id_fk",
+ "tableFrom": "demonstrations",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "bot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_bindings": {
+ "name": "delivery_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identity": {
+ "name": "identity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mention_id": {
+ "name": "mention_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "opted_out_at": {
+ "name": "opted_out_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_binding_identity_idx": {
+ "name": "delivery_binding_identity_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "identity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_bindings_owner_idx": {
+ "name": "delivery_bindings_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_bindings_owner_user_id_users_id_fk": {
+ "name": "delivery_bindings_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_channel_id_channels_id_fk": {
+ "name": "delivery_bindings_channel_id_channels_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_bindings_agent_id_agents_id_fk": {
+ "name": "delivery_bindings_agent_id_agents_id_fk",
+ "tableFrom": "delivery_bindings",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_challenges": {
+ "name": "delivery_challenges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "delivery_challenges_owner_user_id_users_id_fk": {
+ "name": "delivery_challenges_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_channel_id_channels_id_fk": {
+ "name": "delivery_challenges_channel_id_channels_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_challenges_agent_id_agents_id_fk": {
+ "name": "delivery_challenges_agent_id_agents_id_fk",
+ "tableFrom": "delivery_challenges",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_inbox": {
+ "name": "delivery_inbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_inbox_dedupe_idx": {
+ "name": "delivery_inbox_dedupe_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_inbox_owner_idx": {
+ "name": "delivery_inbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_inbox_owner_user_id_users_id_fk": {
+ "name": "delivery_inbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_channel_id_channels_id_fk": {
+ "name": "delivery_inbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_inbox_agent_id_agents_id_fk": {
+ "name": "delivery_inbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_inbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.delivery_outbox": {
+ "name": "delivery_outbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dedupe_key": {
+ "name": "dedupe_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binding_id": {
+ "name": "binding_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "device_id": {
+ "name": "device_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "delivery_outbox_owner_idx": {
+ "name": "delivery_outbox_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "delivery_outbox_provider_idx": {
+ "name": "delivery_outbox_provider_idx",
+ "columns": [
+ {
+ "expression": "transport",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "delivery_outbox_owner_user_id_users_id_fk": {
+ "name": "delivery_outbox_owner_user_id_users_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_channel_id_channels_id_fk": {
+ "name": "delivery_outbox_channel_id_channels_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "delivery_outbox_agent_id_agents_id_fk": {
+ "name": "delivery_outbox_agent_id_agents_id_fk",
+ "tableFrom": "delivery_outbox",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "delivery_outbox_dedupe_key_unique": {
+ "name": "delivery_outbox_dedupe_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "dedupe_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_devices": {
+ "name": "push_devices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_devices_owner_idx": {
+ "name": "push_devices_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_devices_owner_user_id_users_id_fk": {
+ "name": "push_devices_owner_user_id_users_id_fk",
+ "tableFrom": "push_devices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "push_devices_token_unique": {
+ "name": "push_devices_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_bot_threads": {
+ "name": "group_bot_threads",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_bot_threads_thread_idx": {
+ "name": "group_bot_threads_thread_idx",
+ "columns": [
+ {
+ "expression": "thread_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_bot_threads_owner_user_id_users_id_fk": {
+ "name": "group_bot_threads_owner_user_id_users_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_bot_threads_channel_id_channels_id_fk": {
+ "name": "group_bot_threads_channel_id_channels_id_fk",
+ "tableFrom": "group_bot_threads",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "group_bot_threads_owner_user_id_channel_id_agent_id_pk": {
+ "name": "group_bot_threads_owner_user_id_channel_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "channel_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_messages": {
+ "name": "group_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "channel_id": {
+ "name": "channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'completed'"
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_messages_channel_idx": {
+ "name": "group_messages_channel_idx",
+ "columns": [
+ {
+ "expression": "channel_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": {
+ "group_messages_channel_id_channels_id_fk": {
+ "name": "group_messages_channel_id_channels_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "channels",
+ "columnsFrom": [
+ "channel_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_messages_owner_user_id_users_id_fk": {
+ "name": "group_messages_owner_user_id_users_id_fk",
+ "tableFrom": "group_messages",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "group_messages_status_check": {
+ "name": "group_messages_status_check",
+ "value": "\"group_messages\".\"status\" IN ('queued', 'running', 'waiting', 'completed', 'failed')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.action_records": {
+ "name": "action_records",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "surface": {
+ "name": "surface",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "action_records_created_idx": {
+ "name": "action_records_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "action_records_bot_idx": {
+ "name": "action_records_bot_idx",
+ "columns": [
+ {
+ "expression": "bot_id",
+ "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": {
+ "action_records_surface_check": {
+ "name": "action_records_surface_check",
+ "value": "\"action_records\".\"surface\" IN ('cloud', 'local')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.capability_settings": {
+ "name": "capability_settings",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "capability": {
+ "name": "capability",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "capability_settings_scope_kind_scope_id_capability_pk": {
+ "name": "capability_settings_scope_kind_scope_id_capability_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id",
+ "capability"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "capability_settings_scope_kind_check": {
+ "name": "capability_settings_scope_kind_check",
+ "value": "\"capability_settings\".\"scope_kind\" IN ('organization', 'role', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.enterprise_settings": {
+ "name": "enterprise_settings",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.model_usage": {
+ "name": "model_usage",
+ "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": false
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "model_usage_created_idx": {
+ "name": "model_usage_created_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "model_usage_model_idx": {
+ "name": "model_usage_model_idx",
+ "columns": [
+ {
+ "expression": "model",
+ "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": {
+ "model_usage_source_check": {
+ "name": "model_usage_source_check",
+ "value": "\"model_usage\".\"source\" IN ('configured', 'observed', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.network_policies": {
+ "name": "network_policies",
+ "schema": "",
+ "columns": {
+ "scope_kind": {
+ "name": "scope_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_id": {
+ "name": "scope_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rules": {
+ "name": "rules",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"entries\":[]}'::jsonb"
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "network_policies_scope_kind_scope_id_pk": {
+ "name": "network_policies_scope_kind_scope_id_pk",
+ "columns": [
+ "scope_kind",
+ "scope_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "network_policies_scope_kind_check": {
+ "name": "network_policies_scope_kind_check",
+ "value": "\"network_policies\".\"scope_kind\" IN ('organization', 'group')"
+ },
+ "network_policies_mode_check": {
+ "name": "network_policies_mode_check",
+ "value": "\"network_policies\".\"mode\" IN ('allow_all', 'defaults_plus_allowlist', 'allowlist_only')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.scim_connection_bindings": {
+ "name": "scim_connection_bindings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_key": {
+ "name": "connection_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decommissioned_at": {
+ "name": "decommissioned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_status": {
+ "name": "decommission_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "decommission_cursor_user_id": {
+ "name": "decommission_cursor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_reconciled_user_count": {
+ "name": "decommission_reconciled_user_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_batch_count": {
+ "name": "decommission_batch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_revision": {
+ "name": "decommission_revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "decommission_completed_at": {
+ "name": "decommission_completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_id": {
+ "name": "decommission_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decommission_lease_expires_at": {
+ "name": "decommission_lease_expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "scim_connection_bindings_connection_idx": {
+ "name": "scim_connection_bindings_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_connection_bindings_connection_key_unique": {
+ "name": "scim_connection_bindings_connection_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_group_members": {
+ "name": "scim_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "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
+ },
+ "membership_key": {
+ "name": "membership_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_group_members_connection_idx": {
+ "name": "scim_group_members_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_group_idx": {
+ "name": "scim_group_members_group_idx",
+ "columns": [
+ {
+ "expression": "group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_group_members_scim_user_idx": {
+ "name": "scim_group_members_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_group_members_group_id_scim_groups_id_fk": {
+ "name": "scim_group_members_group_id_scim_groups_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_groups",
+ "columnsFrom": [
+ "group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_group_members_scim_user_id_scim_users_id_fk": {
+ "name": "scim_group_members_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_group_members",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_group_members_membership_key_unique": {
+ "name": "scim_group_members_membership_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "membership_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_groups": {
+ "name": "scim_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name_key": {
+ "name": "display_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_groups_connection_idx": {
+ "name": "scim_groups_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_groups_domain_idx": {
+ "name": "scim_groups_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_groups_display_name_key_unique": {
+ "name": "scim_groups_display_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "display_name_key"
+ ]
+ },
+ "scim_groups_external_id_key_unique": {
+ "name": "scim_groups_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_groups_order_key_unique": {
+ "name": "scim_groups_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_identity_tombstones": {
+ "name": "scim_identity_tombstones",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile": {
+ "name": "profile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_identity_tombstones_connection_idx": {
+ "name": "scim_identity_tombstones_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_domain_idx": {
+ "name": "scim_identity_tombstones_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_identity_tombstones_user_idx": {
+ "name": "scim_identity_tombstones_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_identity_tombstones_user_id_users_id_fk": {
+ "name": "scim_identity_tombstones_user_id_users_id_fk",
+ "tableFrom": "scim_identity_tombstones",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_identity_tombstones_external_id_key_unique": {
+ "name": "scim_identity_tombstones_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_projection_grants": {
+ "name": "scim_projection_grants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scim_user_id": {
+ "name": "scim_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_kind": {
+ "name": "source_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_value": {
+ "name": "source_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "grant_key": {
+ "name": "grant_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_projection_grants_connection_idx": {
+ "name": "scim_projection_grants_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_domain_idx": {
+ "name": "scim_projection_grants_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_scim_user_idx": {
+ "name": "scim_projection_grants_scim_user_idx",
+ "columns": [
+ {
+ "expression": "scim_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_projection_grants_user_idx": {
+ "name": "scim_projection_grants_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_projection_grants_scim_user_id_scim_users_id_fk": {
+ "name": "scim_projection_grants_scim_user_id_scim_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "scim_users",
+ "columnsFrom": [
+ "scim_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scim_projection_grants_user_id_users_id_fk": {
+ "name": "scim_projection_grants_user_id_users_id_fk",
+ "tableFrom": "scim_projection_grants",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_projection_grants_grant_key_unique": {
+ "name": "scim_projection_grants_grant_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "grant_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_subjects": {
+ "name": "scim_subjects",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "profile_source_id": {
+ "name": "profile_source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_subjects_profile_source_idx": {
+ "name": "scim_subjects_profile_source_idx",
+ "columns": [
+ {
+ "expression": "profile_source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_subjects_user_id_users_id_fk": {
+ "name": "scim_subjects_user_id_users_id_fk",
+ "tableFrom": "scim_subjects",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_subjects_user_id_unique": {
+ "name": "scim_subjects_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scim_users": {
+ "name": "scim_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provisioning_domain_id": {
+ "name": "provisioning_domain_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connection_user_key": {
+ "name": "connection_user_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name": {
+ "name": "user_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_name_key": {
+ "name": "user_name_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "primary_email": {
+ "name": "primary_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "work_email_value_index": {
+ "name": "work_email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_value_index": {
+ "name": "email_value_index",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "formatted_name": {
+ "name": "formatted_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "given_name": {
+ "name": "given_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "family_name": {
+ "name": "family_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serialized_emails": {
+ "name": "serialized_emails",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serialized_attributes": {
+ "name": "serialized_attributes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id_key": {
+ "name": "external_id_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "scim_users_connection_idx": {
+ "name": "scim_users_connection_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_domain_idx": {
+ "name": "scim_users_domain_idx",
+ "columns": [
+ {
+ "expression": "provisioning_domain_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "scim_users_user_idx": {
+ "name": "scim_users_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "scim_users_user_id_users_id_fk": {
+ "name": "scim_users_user_id_users_id_fk",
+ "tableFrom": "scim_users",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "scim_users_connection_user_key_unique": {
+ "name": "scim_users_connection_user_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "connection_user_key"
+ ]
+ },
+ "scim_users_user_name_key_unique": {
+ "name": "scim_users_user_name_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_name_key"
+ ]
+ },
+ "scim_users_external_id_key_unique": {
+ "name": "scim_users_external_id_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "external_id_key"
+ ]
+ },
+ "scim_users_order_key_unique": {
+ "name": "scim_users_order_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "order_key"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bot_lifecycle": {
+ "name": "bot_lifecycle",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notify": {
+ "name": "notify",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bot_lifecycle_owner_user_id_users_id_fk": {
+ "name": "bot_lifecycle_owner_user_id_users_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bot_lifecycle_agent_id_agents_id_fk": {
+ "name": "bot_lifecycle_agent_id_agents_id_fk",
+ "tableFrom": "bot_lifecycle",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bot_lifecycle_owner_user_id_agent_id_pk": {
+ "name": "bot_lifecycle_owner_user_id_agent_id_pk",
+ "columns": [
+ "owner_user_id",
+ "agent_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.update_routing_preferences": {
+ "name": "update_routing_preferences",
+ "schema": "",
+ "columns": {
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "update_routing_preferences_owner_user_id_users_id_fk": {
+ "name": "update_routing_preferences_owner_user_id_users_id_fk",
+ "tableFrom": "update_routing_preferences",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "update_routing_preferences_owner_user_id_kind_pk": {
+ "name": "update_routing_preferences_owner_user_id_kind_pk",
+ "columns": [
+ "owner_user_id",
+ "kind"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.saved_logins": {
+ "name": "saved_logins",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_password": {
+ "name": "encrypted_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "saved_logins_owner_origin_username_idx": {
+ "name": "saved_logins_owner_origin_username_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "origin",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "username",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "saved_logins_owner_idx": {
+ "name": "saved_logins_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "saved_logins_owner_user_id_users_id_fk": {
+ "name": "saved_logins_owner_user_id_users_id_fk",
+ "tableFrom": "saved_logins",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sign_in_requests": {
+ "name": "sign_in_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "owner_user_id": {
+ "name": "owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bot_id": {
+ "name": "bot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "origin": {
+ "name": "origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thread_id": {
+ "name": "thread_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "control_request_id": {
+ "name": "control_request_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "continuation": {
+ "name": "continuation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filling_until": {
+ "name": "filling_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "sign_in_requests_owner_idx": {
+ "name": "sign_in_requests_owner_idx",
+ "columns": [
+ {
+ "expression": "owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sign_in_requests_owner_user_id_users_id_fk": {
+ "name": "sign_in_requests_owner_user_id_users_id_fk",
+ "tableFrom": "sign_in_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "owner_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "sign_in_requests_status_check": {
+ "name": "sign_in_requests_status_check",
+ "value": "\"sign_in_requests\".\"status\" IN ('pending', 'filling', 'code_required', 'taken_over', 'signed_in', 'failed', 'cancelled', 'expired')"
+ },
+ "sign_in_requests_method_check": {
+ "name": "sign_in_requests_method_check",
+ "value": "\"sign_in_requests\".\"method\" IS NULL OR \"sign_in_requests\".\"method\" IN ('typed', 'saved', 'takeover')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_assignments": {
+ "name": "team_bot_assignments",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_assignments_agent_id_agents_id_fk": {
+ "name": "team_bot_assignments_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_assignments_assigned_by_users_id_fk": {
+ "name": "team_bot_assignments_assigned_by_users_id_fk",
+ "tableFrom": "team_bot_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "assigned_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_assignments_agent_id_group_name_pk": {
+ "name": "team_bot_assignments_agent_id_group_name_pk",
+ "columns": [
+ "agent_id",
+ "group_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_audience": {
+ "name": "team_bot_audience",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_audience_agent_id_agents_id_fk": {
+ "name": "team_bot_audience_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_audience",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_audience_agent_id_kind_value_pk": {
+ "name": "team_bot_audience_agent_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_audience_kind_check": {
+ "name": "team_bot_audience_kind_check",
+ "value": "\"team_bot_audience\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consent_defaults": {
+ "name": "team_bot_consent_defaults",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consent_defaults_user_id_users_id_fk": {
+ "name": "team_bot_consent_defaults_user_id_users_id_fk",
+ "tableFrom": "team_bot_consent_defaults",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consent_defaults_user_id_server_id_pk": {
+ "name": "team_bot_consent_defaults_user_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_bot_consents": {
+ "name": "team_bot_consents",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_consents_user_id_users_id_fk": {
+ "name": "team_bot_consents_user_id_users_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_consents_agent_id_agents_id_fk": {
+ "name": "team_bot_consents_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_consents",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "team_bot_consents_user_id_agent_id_server_id_pk": {
+ "name": "team_bot_consents_user_id_agent_id_server_id_pk",
+ "columns": [
+ "user_id",
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_consents_decision_check": {
+ "name": "team_bot_consents_decision_check",
+ "value": "\"team_bot_consents\".\"decision\" IN ('always', 'once')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.team_bot_publications": {
+ "name": "team_bot_publications",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "published_by": {
+ "name": "published_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_bot_publications_agent_id_agents_id_fk": {
+ "name": "team_bot_publications_agent_id_agents_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_bot_publications_published_by_users_id_fk": {
+ "name": "team_bot_publications_published_by_users_id_fk",
+ "tableFrom": "team_bot_publications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "published_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "team_bot_publications_audience_check": {
+ "name": "team_bot_publications_audience_check",
+ "value": "\"team_bot_publications\".\"audience\" IN ('team', 'people')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approval_members": {
+ "name": "shared_use_approval_members",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approval_members_approval_fk": {
+ "name": "shared_use_approval_members_approval_fk",
+ "tableFrom": "shared_use_approval_members",
+ "tableTo": "shared_use_approvals",
+ "columnsFrom": [
+ "agent_id",
+ "server_id"
+ ],
+ "columnsTo": [
+ "agent_id",
+ "server_id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approval_members_agent_id_server_id_kind_value_pk": {
+ "name": "shared_use_approval_members_agent_id_server_id_kind_value_pk",
+ "columns": [
+ "agent_id",
+ "server_id",
+ "kind",
+ "value"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approval_members_kind_check": {
+ "name": "shared_use_approval_members_kind_check",
+ "value": "\"shared_use_approval_members\".\"kind\" IN ('user', 'group')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_approvals": {
+ "name": "shared_use_approvals",
+ "schema": "",
+ "columns": {
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "audience": {
+ "name": "audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "outside_input": {
+ "name": "outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "shared_use_approvals_agent_id_agents_id_fk": {
+ "name": "shared_use_approvals_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_approvals_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_approvals_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_approvals",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "shared_use_approvals_agent_id_server_id_pk": {
+ "name": "shared_use_approvals_agent_id_server_id_pk",
+ "columns": [
+ "agent_id",
+ "server_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_approvals_audience_check": {
+ "name": "shared_use_approvals_audience_check",
+ "value": "\"shared_use_approvals\".\"audience\" IN ('owner', 'people', 'team')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.shared_use_requests": {
+ "name": "shared_use_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "agent_id": {
+ "name": "agent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_audience": {
+ "name": "proposed_audience",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_outside_input": {
+ "name": "proposed_outside_input",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "proposed_members": {
+ "name": "proposed_members",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "shared_use_requests_pending_idx": {
+ "name": "shared_use_requests_pending_idx",
+ "columns": [
+ {
+ "expression": "agent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"shared_use_requests\".\"status\" = 'pending'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "shared_use_requests_status_idx": {
+ "name": "shared_use_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "shared_use_requests_agent_id_agents_id_fk": {
+ "name": "shared_use_requests_agent_id_agents_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "agents",
+ "columnsFrom": [
+ "agent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "shared_use_requests_server_id_mcp_servers_id_fk": {
+ "name": "shared_use_requests_server_id_mcp_servers_id_fk",
+ "tableFrom": "shared_use_requests",
+ "tableTo": "mcp_servers",
+ "columnsFrom": [
+ "server_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "shared_use_requests_audience_check": {
+ "name": "shared_use_requests_audience_check",
+ "value": "\"shared_use_requests\".\"proposed_audience\" IN ('owner', 'people', 'team')"
+ },
+ "shared_use_requests_reason_check": {
+ "name": "shared_use_requests_reason_check",
+ "value": "\"shared_use_requests\".\"reason\" IN ('publish', 'trigger', 'grant', 'refused_call')"
+ },
+ "shared_use_requests_status_check": {
+ "name": "shared_use_requests_status_check",
+ "value": "\"shared_use_requests\".\"status\" IN ('pending', 'approved', 'declined', 'superseded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.identity_link_challenges": {
+ "name": "identity_link_challenges",
+ "schema": "",
+ "columns": {
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "identity_link_challenges_expiry_idx": {
+ "name": "identity_link_challenges_expiry_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_link_challenges_user_provider_idx": {
+ "name": "identity_link_challenges_user_provider_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_links": {
+ "name": "identity_links",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "realm": {
+ "name": "realm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "handle": {
+ "name": "handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verified_by": {
+ "name": "verified_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identity_links_identity_idx": {
+ "name": "identity_links_identity_idx",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "subject",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_user_realm_idx": {
+ "name": "identity_links_user_realm_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "realm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "identity_links_credential_idx": {
+ "name": "identity_links_credential_idx",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"identity_links\".\"credential_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "identity_links_credential_id_credentials_id_fk": {
+ "name": "identity_links_credential_id_credentials_id_fk",
+ "tableFrom": "identity_links",
+ "tableTo": "credentials",
+ "columnsFrom": [
+ "credential_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "identity_links_verified_by_check": {
+ "name": "identity_links_verified_by_check",
+ "value": "\"identity_links\".\"verified_by\" IN ('challenge', 'oauth')"
+ },
+ "identity_links_status_check": {
+ "name": "identity_links_status_check",
+ "value": "\"identity_links\".\"status\" IN ('active', 'needs_reconnect')"
+ }
+ },
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.agent_type": {
+ "name": "agent_type",
+ "schema": "public",
+ "values": [
+ "built_in",
+ "remote_ag_ui",
+ "remote_mastra"
+ ]
+ },
+ "public.credential_kind": {
+ "name": "credential_kind",
+ "schema": "public",
+ "values": [
+ "model",
+ "connector",
+ "agent",
+ "mcp",
+ "mcp_oauth_client",
+ "mcp_user_token"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "user"
+ ]
+ },
+ "public.agent_visibility": {
+ "name": "agent_visibility",
+ "schema": "public",
+ "values": [
+ "public",
+ "private"
+ ]
+ },
+ "public.routine_run_status": {
+ "name": "routine_run_status",
+ "schema": "public",
+ "values": [
+ "succeeded",
+ "failed",
+ "skipped",
+ "waiting"
+ ]
+ },
+ "public.responsibility_run_status": {
+ "name": "responsibility_run_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "running",
+ "waiting",
+ "succeeded",
+ "failed",
+ "skipped"
+ ]
+ },
+ "public.responsibility_status": {
+ "name": "responsibility_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json
index 70e4dfa60..10108fb72 100644
--- a/server/drizzle/meta/_journal.json
+++ b/server/drizzle/meta/_journal.json
@@ -400,6 +400,55 @@
"when": 1791866936937,
"tag": "0056_sign_in_code_step",
"breakpoints": true
+ },
+ {
+ "idx": 57,
+ "version": "7",
+ "when": 1791908858814,
+ "tag": "0057_arcade_provider",
+ "breakpoints": true
+ },
+ {
+ "idx": 58,
+ "version": "7",
+ "when": 1791924168752,
+ "tag": "0058_pending_authorization_checks",
+ "breakpoints": true
+ },
+ {
+ "idx": 59,
+ "version": "7",
+ "when": 1791942141206,
+ "tag": "0059_pending_authorization_completion_pair",
+ "breakpoints": true
+ },
+ {
+ "idx": 60,
+ "version": "7",
+ "when": 1791943538845,
+ "tag": "0060_pending_authorization_person_keys",
+ "breakpoints": true
+ },
+ {
+ "idx": 61,
+ "version": "7",
+ "when": 1791943598343,
+ "tag": "0061_settle_brokered_account_mode",
+ "breakpoints": true
+ },
+ {
+ "idx": 62,
+ "version": "7",
+ "when": 1791994602603,
+ "tag": "0062_pending_authorization_user_is_actor",
+ "breakpoints": true
+ },
+ {
+ "idx": 63,
+ "version": "7",
+ "when": 1792012124232,
+ "tag": "0063_mcp_tool_vendor_name",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
index df7114e93..175943146 100644
--- a/server/package.json
+++ b/server/package.json
@@ -7,7 +7,7 @@
"scripts": {
"build": "tsc --noEmit",
"db:generate": "bun --env-file=../.env drizzle-kit generate --config=drizzle.config.ts",
- "db:migrate": "bun --env-file=../.env drizzle-kit migrate --config=drizzle.config.ts",
+ "db:migrate": "bun --env-file=../.env scripts/migrate.ts",
"dev": "bun --env-file=../.env --watch src/index.ts",
"typecheck": "tsc --noEmit"
},
@@ -15,6 +15,7 @@
"@ag-ui/client": "1.0.1",
"@ag-ui/core": "1.0.1",
"@ag-ui/mastra": "1.1.6",
+ "@arcadeai/arcadejs": "2.4.1",
"@better-auth/drizzle-adapter": "^1.7.1",
"@better-auth/electron": "1.7.1",
"@better-auth/expo": "1.7.1",
diff --git a/server/scripts/migrate.ts b/server/scripts/migrate.ts
index e4dddbeda..411d1d8ad 100644
--- a/server/scripts/migrate.ts
+++ b/server/scripts/migrate.ts
@@ -9,35 +9,80 @@
*
* The migrator underneath it is part of `drizzle-orm`, which is a runtime dependency because the
* server imports it anyway. It needs a connection and the folder of SQL files, both of which are in
- * the image, and it keeps the same `drizzle.__drizzle_migrations` journal the CLI does, so the two
- * are interchangeable and a database migrated by either is migrated.
+ * the image, and it keeps its journal where `MIGRATIONS_JOURNAL` in `src/db/apply-migrations` says.
+ * That is drizzle's default table, the one the CLI uses when `drizzle.config.ts` names none, so a
+ * database migrated by the CLI before this script existed is read here without translation, and
+ * the two were interchangeable — for exactly as long as those two names stay in step.
+ *
+ * It is now what every path runs — the image, the Helm Job, the compose stack, CI, and `db:migrate`
+ * — because the two stopped being interchangeable in the direction that matters: only this one
+ * refuses a database holding a stamp newer than a migration it is about to walk past. See
+ * `src/db/migration-reach`.
*/
import { join } from "node:path";
-import { drizzle } from "drizzle-orm/postgres-js";
-import { migrate } from "drizzle-orm/postgres-js/migrator";
-import postgres from "postgres";
-
-const databaseUrl = process.env.DATABASE_URL;
-if (!databaseUrl) {
- throw new Error(
- "DATABASE_URL must be configured before running a database migration command",
- );
-}
+import postgres, { type Sql } from "postgres";
+import { applyMigrations } from "../src/db/apply-migrations";
-/*
- * One connection, and `max: 1`.
+/**
+ * The whole run against one connection, and the connection released afterwards.
*
- * Migrations are a single ordered conversation with the database, and a pool would let two
- * statements that must be ordered land on different connections.
+ * Exported, and the work injectable, so that what happens when the release itself fails can be
+ * tested without a database that refuses to close. `tests/migrate-teardown.test.ts`.
*/
-const client = postgres(databaseUrl, { max: 1, onnotice: () => {} });
-
-try {
- await migrate(drizzle(client), {
- migrationsFolder: join(import.meta.dir, "..", "drizzle"),
- });
- console.info(JSON.stringify({ type: "migrations-applied", status: "ok" }));
-} finally {
- // Released whatever happened, so a failure exits rather than hanging on an open socket.
+export async function runMigrations(
+ client: Sql,
+ migrationsFolder: string,
+ apply: typeof applyMigrations = applyMigrations,
+): Promise {
+ try {
+ /*
+ * Guarded, because the migrator's own idea of "applied" is one number.
+ *
+ * It runs a migration when the journal stamps it later than the newest stamp the database holds,
+ * and it has nothing to say about a migration stamped earlier that it has never run. See
+ * `src/db/migration-reach`: a database that ran a branch's migrations before that branch was
+ * merged and renumbered holds a stamp newer than the migrations the merge brought in, and the
+ * unguarded migrator walks past them and prints the line below anyway.
+ */
+ await apply(client, migrationsFolder);
+ console.info(JSON.stringify({ type: "migrations-applied", status: "ok" }));
+ } catch (failure) {
+ /*
+ * Released whatever happened, so a failure exits rather than hanging on an open socket — but
+ * NOT with an awaited `end()` in a `finally`. A rejection there replaces the error in flight,
+ * and the error in flight is the one this script exists to deliver: the refusal naming the
+ * migrations a database walked past would leave as a bare socket error. The release's own
+ * failure is reported beside it, and the run's failure is the one that propagates.
+ */
+ await client.end({ timeout: 5 }).catch((teardown: unknown) => {
+ console.error(
+ JSON.stringify({
+ type: "migrations-connection-release-failed",
+ error:
+ teardown instanceof Error ? teardown.message : String(teardown),
+ }),
+ );
+ });
+ throw failure;
+ }
+ // After a clean run a failed release has nothing to hide, so it is the run's outcome.
await client.end({ timeout: 5 });
}
+
+if (import.meta.main) {
+ const databaseUrl = process.env.DATABASE_URL;
+ if (!databaseUrl) {
+ throw new Error(
+ "DATABASE_URL must be configured before running a database migration command",
+ );
+ }
+
+ /*
+ * One connection, and `max: 1`.
+ *
+ * Migrations are a single ordered conversation with the database, and a pool would let two
+ * statements that must be ordered land on different connections.
+ */
+ const client = postgres(databaseUrl, { max: 1, onnotice: () => {} });
+ await runMigrations(client, join(import.meta.dir, "..", "drizzle"));
+}
diff --git a/server/scripts/test-network-guard.ts b/server/scripts/test-network-guard.ts
new file mode 100644
index 000000000..bf52b4516
--- /dev/null
+++ b/server/scripts/test-network-guard.ts
@@ -0,0 +1,517 @@
+/**
+ * The test process's egress, closed at the transport rather than asked for politely file by file.
+ *
+ * A test that dials a vendor stops being a fact about this repository. It passes or fails on the
+ * vendor's uptime, on DNS and on whatever the network did that minute, so a red says nothing and a
+ * green says less than it looks like; it is slow and flaky in a suite this size; it carries
+ * whatever the test handed it — an API key, a user id, a return address — to a third party; and it
+ * is a real request against a real service, made from CI, on every run.
+ *
+ * EVERY TEST FILE ALREADY MEANT TO STUB, AND STUBBING PER FILE IS WHAT FAILED. `server/tests/
+ * composio-broker-seam.test.ts` assigned `globalThis.fetch` around the call it was about and still
+ * reached `https://backend.composio.dev/api/v3.1/auth_configs` on every run, because the vendor
+ * client had been constructed one line EARLIER and `@composio/client` binds its transport once, at
+ * construction (`this.fetch = options.fetch ?? Shims.getDefaultFetch()`, `client.mjs:122`). A stub
+ * installed after that point is a stub the SDK never sees. That failure mode is invisible in review
+ * — the file reads as if it stubs — which is why the floor is here, in a preload that runs before
+ * any test file is imported and therefore before any vendor client can capture a transport.
+ *
+ * WHAT IS REFUSED: a connection to anything that is not loopback, through every way this runtime
+ * (Bun 1.4) offers a test file of opening one. Patching `globalThis.fetch` and `http.request` alone
+ * is not that: an earlier version of this file did only that, and `https.get`, `Bun.fetch`, the
+ * named imports `import { get } from "node:https"`, the built-in `undici`, `net`, `tls`,
+ * `Bun.connect`, `WebSocket` and `dgram` all still reached example.com. Each one is closed below,
+ * at the lowest layer JavaScript can reach for it:
+ *
+ * - `Bun.fetch` is a read-only, non-configurable property and cannot be replaced, and the built-in
+ * `undici` captured the native fetch before this file ran. Both honour the proxy environment, so
+ * the process's HTTP(S) proxy is pointed at a refusing listener on loopback (`NO_PROXY` keeps the
+ * suite's own loopback servers direct). Every native fetch to a far host is handed to that
+ * listener, which records it and answers 403 without ever opening a socket onward.
+ * - `globalThis.fetch` is still replaced too, so the common path rejects with the explanation
+ * below instead of resolving a 403.
+ * - `node:http`/`node:https` do NOT read the proxy environment, so `request`, `get` and
+ * `ClientRequest` are replaced on both, and `syncBuiltinESMExports()` pushes the replacements into
+ * the named-import bindings, which Bun otherwise snapshots when the built-in first loads.
+ * - `node:net`/`node:tls` are guarded on `net.Socket.prototype.connect`, which `net.connect`,
+ * `net.createConnection` and `tls.connect` all reach; `Bun.connect`, `node:http2`'s `connect`,
+ * `WebSocket` (global, `ws`, `undici`, `node:http`) and UDP (`dgram`, `Bun.udpSocket`) likewise.
+ *
+ * Raw sockets are allowed to one more place than loopback: the hosts of `TEST_DATABASE_URL` and
+ * `DATABASE_URL`, because that is how the Postgres driver reaches the test database and that host is
+ * deployment configuration, not necessarily loopback.
+ *
+ * AND RAW SOCKETS ARE JUDGED BY THE ADDRESS THEY REACH, because some suites' SUBJECT is the socket
+ * layer. `agent-computer`'s egress filter has to prove it pins the address it checked, falls back
+ * to the next one, and gives up on one that never answers, and it can only prove that by opening
+ * sockets at `rebind.example.test`, `dual.example.test` and `10.255.255.1`. None of those can be a
+ * vendor, and the rule that admits them says why — not a list of the hosts a suite happens to use:
+ * - a LITERAL address the public internet does not route (RFC 1918 private, the RFC 5737/3849
+ * documentation ranges, IPv6 unique-local). Link-local is excluded on purpose: 169.254.169.254 is
+ * the cloud metadata service, which hands out credentials.
+ * - a name under a top-level domain RFC 6761 reserves so public DNS never answers it (`.test`,
+ * `.invalid`, `.example`) — and ONLY with its lookup wrapped, because a `lookup` option or
+ * `/etc/hosts` can map a reserved name to any address. The wrapped lookup lets the connection
+ * proceed only if every address it answers is one a literal would be let through to, so a name
+ * is never the way round the address rule.
+ * This is at `net.Socket#connect` only, where a lookup can be checked. Every other entry point
+ * (`fetch`, `http(s).request`/`get`, `http2`, `WebSocket`) stays loopback-only, and
+ * `Bun.connect`/UDP take the same literal-address rule but no names.
+ * Why not a named opt-out like `OPENBOT_LIVE_*`: `bun run test:ci` runs every file in ONE process,
+ * so a flag one suite set would unguard every file after it.
+ *
+ * WHAT IS NOT GUARDED, AND WHY — none of these is how any code in this repository talks to a vendor:
+ * `Bun.SQL` (native; it IS the database path, and its connection never surfaces to JavaScript);
+ * `Bun.S3Client`/`Bun.redis` (native clients this repository does not use); a `fetch` given an
+ * explicit `proxy` or `unix` option, or a test that rewrites the proxy variables itself; a child
+ * process (`Bun.spawn`, `child_process` running `curl`); a `Worker`, whose global scope does not
+ * run this preload; and DNS lookups, which resolve a name but connect to nothing.
+ * `server/tests/test-network-guard.test.ts` plants a call through every guarded entry point.
+ *
+ * HOW A SUITE THAT IS MEANT TO DIAL OPTS OUT: any `OPENBOT_LIVE_*` variable with a non-empty value.
+ * The three live suites already set one — `OPENBOT_LIVE_COMPOSIO`, `OPENBOT_LIVE_ARCADE` and
+ * `OPENBOT_LIVE_SCREEN` are set by their `test:live-*` scripts in `package.json`, which also refuse
+ * to run without a key, so a live suite remains deliberate and credentialed and stays skipped
+ * otherwise.
+ */
+
+import dgram from "node:dgram";
+import dns from "node:dns";
+import http from "node:http";
+import http2 from "node:http2";
+import https from "node:https";
+import { syncBuiltinESMExports } from "node:module";
+import net from "node:net";
+
+const liveRun = Object.entries(process.env).some(
+ ([name, value]) => name.startsWith("OPENBOT_LIVE_") && (value ?? "").trim(),
+);
+
+/** Loopback, in every spelling a URL or a Node request option can arrive in. */
+function isLoopback(host: string): boolean {
+ const bare = host.replace(/^\[|\]$/g, "").toLowerCase();
+ return (
+ bare === "localhost" ||
+ bare === "::1" ||
+ bare === "0.0.0.0" ||
+ bare === "::" ||
+ bare === "" ||
+ bare.endsWith(".localhost") ||
+ /^127\./.test(bare) ||
+ /^::ffff:127\./.test(bare)
+ );
+}
+
+/** The configured database hosts, read when asked, since a test may point either variable elsewhere. */
+function isDatabaseHost(host: string): boolean {
+ const bare = host.replace(/^\[|\]$/g, "").toLowerCase();
+ return [process.env.TEST_DATABASE_URL, process.env.DATABASE_URL].some(
+ (configured) => {
+ if (!configured) return false;
+ try {
+ return (
+ new URL(configured).hostname.replace(/^\[|\]$/g, "").toLowerCase() ===
+ bare
+ );
+ } catch {
+ return false;
+ }
+ },
+ );
+}
+
+/**
+ * Every target this guard refused, in order, on the global so a test can read it.
+ *
+ * A refusal does not always reach the caller as itself: `@composio/client` retries a rejected
+ * transport and then reports "Connection error.", which is the same sentence a real outage would
+ * produce, and a native `Bun.fetch` sees the refusing proxy's 403. So the fact that the guard —
+ * rather than the network — stopped a call is recorded here at the point of refusal, where it is
+ * unambiguous. `server/tests/test-network-guard.test.ts` reads it; nothing in `server/src` may.
+ */
+declare global {
+ var openbotTestNetworkRefusals: string[] | undefined;
+}
+
+globalThis.openbotTestNetworkRefusals = [];
+
+const explanation =
+ "Tests do not use the network: stub the transport this call goes through (and stub it BEFORE " +
+ "constructing any client, which binds `fetch` once), or, for a deliberate vendor suite, set an " +
+ "OPENBOT_LIVE_* variable as `bun run test:live-*` does. Guard: server/scripts/test-network-guard.ts.";
+
+function refuse(target: string): Error {
+ globalThis.openbotTestNetworkRefusals?.push(target);
+ return new Error(`A test tried to reach ${target}. ${explanation}`);
+}
+
+/** The host a Node `request`/`get`/`ClientRequest` call is aimed at, from any of its overloads. */
+function nodeRequestHost(args: unknown[]): string {
+ let host = "";
+ const [first, second] = args;
+ if (typeof first === "string") host = new URL(first).hostname;
+ else if (first instanceof URL) host = first.hostname;
+ for (const options of [first, second]) {
+ if (options && typeof options === "object" && !(options instanceof URL)) {
+ const { hostname, host: hostWithPort } = options as {
+ hostname?: string | null;
+ host?: string | null;
+ };
+ const named = hostname ?? hostWithPort;
+ if (named) host = String(named).replace(/:\d+$/, "");
+ }
+ }
+ return host;
+}
+
+/** The host a `net.Socket#connect` is aimed at; `null` for a Unix socket path, which is local. */
+function socketConnectHost(args: unknown[]): string | null {
+ const [first, second] = args;
+ if (Array.isArray(first)) return socketConnectHost(first);
+ if (first && typeof first === "object") {
+ const options = first as { path?: string; host?: string };
+ if (options.path) return null;
+ return options.host ?? "localhost";
+ }
+ if (typeof first === "string" && !/^\d+$/.test(first)) return null;
+ return typeof second === "string" ? second : "localhost";
+}
+
+/**
+ * Addresses no vendor can be behind, because the public internet does not route them: RFC 1918
+ * private space, the RFC 5737 / RFC 3849 documentation ranges, and IPv6 unique-local space
+ * (RFC 4193). Loopback is judged by `isLoopback`. Link-local (169.254/16, fe80::/10) is
+ * deliberately NOT here: 169.254.169.254 is the cloud metadata service, which hands out credentials.
+ */
+const unroutable = new net.BlockList();
+unroutable.addSubnet("10.0.0.0", 8, "ipv4");
+unroutable.addSubnet("172.16.0.0", 12, "ipv4");
+unroutable.addSubnet("192.168.0.0", 16, "ipv4");
+unroutable.addSubnet("192.0.2.0", 24, "ipv4");
+unroutable.addSubnet("198.51.100.0", 24, "ipv4");
+unroutable.addSubnet("203.0.113.0", 24, "ipv4");
+unroutable.addSubnet("fc00::", 7, "ipv6");
+unroutable.addSubnet("2001:db8::", 32, "ipv6");
+
+/** A LITERAL address that is loopback or that the public internet does not route. Never a name. */
+function isUnroutableAddress(host: string): boolean {
+ const bare = host.replace(/^\[|\]$/g, "").toLowerCase();
+ const family = net.isIP(bare);
+ if (family === 0) return false;
+ return (
+ isLoopback(bare) || unroutable.check(bare, family === 6 ? "ipv6" : "ipv4")
+ );
+}
+
+/**
+ * A name under a top-level domain RFC 6761 reserves so that public DNS never answers for it:
+ * `.test`, `.invalid`, `.example` (`.localhost` is loopback already).
+ *
+ * BEING RESERVED IS NOT ENOUGH ON ITS OWN, so no entry point lets such a name through bare:
+ * `/etc/hosts`, a local resolver, or a `lookup` option the caller hands in can map any name to any
+ * address. A special-use name is let through only at `net.Socket#connect`, where its lookup is
+ * replaced by `checkedLookup`, which judges the ADDRESS it resolves to by the same rule as a literal.
+ */
+function isSpecialUseName(host: string): boolean {
+ return net.isIP(host) === 0 && /\.(test|invalid|example)\.?$/i.test(host);
+}
+
+function guardSocketHost(host: string | null, label: string): void {
+ if (
+ host === null ||
+ isLoopback(host) ||
+ isDatabaseHost(host) ||
+ isUnroutableAddress(host)
+ )
+ return;
+ throw refuse(`${label} ${host}`);
+}
+
+type LookupCallback = (
+ error: Error | null,
+ address?: string | Array<{ address: string; family: number }>,
+ family?: number,
+) => void;
+type Lookup = (
+ hostname: string,
+ options: object | LookupCallback,
+ callback?: LookupCallback,
+) => void;
+
+/**
+ * The caller's `lookup` (or the system resolver), passed on only when every address it answers is
+ * one `guardSocketHost` would let a literal through to. Otherwise the socket gets the refusal as its
+ * connection error and never dials the address.
+ */
+function checkedLookup(lookup: Lookup | undefined): Lookup {
+ const resolve = lookup ?? (dns.lookup as unknown as Lookup);
+ return (hostname, options, callback) => {
+ const done = (
+ typeof options === "function" ? options : callback
+ ) as LookupCallback;
+ resolve(
+ hostname,
+ typeof options === "function" ? {} : options,
+ (error, address, family) => {
+ if (error) return done(error);
+ const answered = Array.isArray(address)
+ ? address.map((entry) => entry.address)
+ : [String(address)];
+ const far = answered.find(
+ (candidate) => !isUnroutableAddress(candidate),
+ );
+ if (far !== undefined)
+ return done(refuse(`socket ${hostname} resolved to ${far}`));
+ done(null, address, family);
+ },
+ );
+ };
+}
+
+/**
+ * `net.Socket#connect` arguments aimed at a special-use name, rewritten with the lookup checked.
+ * The overloads a name arrives in — `(options, cb)`, `(port, host, cb)`, and the pre-normalized
+ * `[options, cb]` array Node's own `net.connect` passes — are covered. `null` for any other shape,
+ * which `guardSocketHost` then refuses like any other name.
+ */
+function withCheckedLookup(args: unknown[]): unknown[] | null {
+ const [first, second] = args;
+ if (Array.isArray(first)) {
+ // The normalized array is tagged by the runtime (a copy is not recognised as one and is
+ // rejected as missing arguments), so it is rewritten in place rather than rebuilt.
+ const inner = withCheckedLookup(first);
+ if (!inner) return null;
+ first.splice(0, first.length, ...inner);
+ return args;
+ }
+ if (first && typeof first === "object") {
+ const options = first as { lookup?: Lookup };
+ return [
+ { ...options, lookup: checkedLookup(options.lookup) },
+ ...args.slice(1),
+ ];
+ }
+ if (
+ (typeof first === "number" || /^\d+$/.test(String(first))) &&
+ typeof second === "string"
+ ) {
+ return [
+ { port: Number(first), host: second, lookup: checkedLookup(undefined) },
+ ...args.slice(2),
+ ];
+ }
+ return null;
+}
+
+type Callable = (...args: unknown[]) => unknown;
+type Constructable = new (...args: unknown[]) => object;
+
+function replace(owner: object, key: string, value: unknown): void {
+ (owner as Record)[key] = value;
+}
+
+if (!liveRun) {
+ /*
+ * The refusing proxy, for every native fetch this file cannot wrap. `Bun.listen` binds
+ * synchronously, so the port is known before the variables naming it are set, and the listener
+ * is unreferenced so it never holds a finished run open.
+ */
+ const refusingProxy = Bun.listen({
+ hostname: "127.0.0.1",
+ port: 0,
+ socket: {
+ data(socket, chunk) {
+ const [method = "", target = ""] = chunk
+ .toString("latin1")
+ .split("\r\n", 1)[0]
+ .split(" ");
+ refuse(
+ method === "CONNECT"
+ ? `https://${target.replace(/:443$/, "")}`
+ : target.replace(/\?.*$/, ""),
+ );
+ const body = `Refused by the test network guard. ${explanation}`;
+ socket.end(
+ "HTTP/1.1 403 Forbidden\r\nConnection: close\r\n" +
+ "X-Openbot-Test-Network-Guard: refused\r\nContent-Type: text/plain\r\n" +
+ `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`,
+ );
+ },
+ },
+ });
+ refusingProxy.unref();
+ const proxy = `http://127.0.0.1:${refusingProxy.port}`;
+ const direct = "localhost,127.0.0.1,::1,[::1],0.0.0.0,.localhost";
+ for (const name of ["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"])
+ process.env[name] = proxy;
+ for (const name of ["NO_PROXY", "no_proxy"]) process.env[name] = direct;
+
+ const passThrough = globalThis.fetch;
+ globalThis.fetch = ((input: unknown, init?: RequestInit) => {
+ const raw =
+ typeof input === "string"
+ ? input
+ : input instanceof URL
+ ? input.href
+ : ((input as { url?: string } | null)?.url ?? String(input));
+ let url: URL | null = null;
+ try {
+ url = new URL(raw);
+ } catch {
+ // A relative or unparseable target reaches no host; the runtime's own error is the right one.
+ }
+ if (url && /^(https?|wss?):$/.test(url.protocol)) {
+ if (!isLoopback(url.hostname)) {
+ return Promise.reject(refuse(url.origin + url.pathname));
+ }
+ }
+ return passThrough(input as RequestInfo | URL, init);
+ }) as typeof globalThis.fetch;
+
+ /*
+ * `request`, `get` and `ClientRequest` are forwarded through an `unknown[]` signature rather than
+ * one of their overloads: nothing here reads an argument it did not already read off the first
+ * two, and restating the overloads would be a declaration to maintain against Node's. `get` is
+ * replaced on its own because the built-in calls its internal `request`, not this property.
+ */
+ for (const module of [http, https] as const) {
+ for (const key of ["request", "get"] as const) {
+ const original = module[key] as unknown as Callable;
+ replace(module, key, (...args: unknown[]) => {
+ const host = nodeRequestHost(args);
+ if (!isLoopback(host)) throw refuse(host);
+ return original(...args);
+ });
+ }
+ }
+ const OriginalClientRequest = http.ClientRequest as unknown as Constructable;
+ replace(
+ http,
+ "ClientRequest",
+ class GuardedClientRequest extends OriginalClientRequest {
+ constructor(...args: unknown[]) {
+ const host = nodeRequestHost(args);
+ if (!isLoopback(host)) throw refuse(host);
+ super(...args);
+ }
+ },
+ );
+
+ const originalSocketConnect = net.Socket.prototype.connect as Callable;
+ replace(
+ net.Socket.prototype,
+ "connect",
+ function (this: net.Socket, ...args: unknown[]) {
+ const host = socketConnectHost(args);
+ if (host !== null && isSpecialUseName(host) && !isDatabaseHost(host)) {
+ const checked = withCheckedLookup(args);
+ if (checked) return originalSocketConnect.apply(this, checked);
+ }
+ guardSocketHost(host, "socket");
+ return originalSocketConnect.apply(this, args);
+ },
+ );
+
+ const originalBunConnect = Bun.connect as unknown as Callable;
+ // `Bun.connect` answers with a promise, so a refusal is a rejection rather than a throw.
+ replace(Bun, "connect", (options: { hostname?: string; unix?: string }) => {
+ try {
+ guardSocketHost(
+ options?.unix ? null : (options?.hostname ?? ""),
+ "socket",
+ );
+ } catch (refusal) {
+ return Promise.reject(refusal);
+ }
+ return originalBunConnect(options);
+ });
+
+ const originalHttp2Connect = http2.connect as unknown as Callable;
+ replace(http2, "connect", (authority: unknown, ...rest: unknown[]) => {
+ const host =
+ authority instanceof URL
+ ? authority.hostname
+ : typeof authority === "string"
+ ? new URL(authority).hostname
+ : ((authority as { hostname?: string; host?: string } | null)
+ ?.hostname ??
+ (authority as { host?: string } | null)?.host ??
+ "");
+ if (!isLoopback(host)) throw refuse(`http2 ${host}`);
+ return originalHttp2Connect(authority, ...rest);
+ });
+
+ const OriginalWebSocket = globalThis.WebSocket as unknown as Constructable;
+ class GuardedWebSocket extends OriginalWebSocket {
+ constructor(...args: unknown[]) {
+ const url = new URL(String(args[0]));
+ if (!isLoopback(url.hostname)) throw refuse(url.origin + url.pathname);
+ super(...args);
+ }
+ }
+ replace(globalThis, "WebSocket", GuardedWebSocket);
+ replace(http, "WebSocket", GuardedWebSocket);
+ for (const builtin of ["ws", "undici"]) {
+ const module = require(builtin) as Record;
+ if (module.WebSocket) replace(module, "WebSocket", GuardedWebSocket);
+ }
+
+ const originalDgramSend = dgram.Socket.prototype.send as Callable;
+ replace(
+ dgram.Socket.prototype,
+ "send",
+ function (this: dgram.Socket, ...args: unknown[]) {
+ // send(msg, port, address?, cb?) or send(msg, offset, length, port, address?, cb?)
+ const address =
+ typeof args[2] === "number" && typeof args[3] === "number"
+ ? args[4]
+ : args[2];
+ if (typeof address === "string") guardSocketHost(address, "udp");
+ return originalDgramSend.apply(this, args);
+ },
+ );
+ const originalDgramConnect = dgram.Socket.prototype.connect as Callable;
+ replace(
+ dgram.Socket.prototype,
+ "connect",
+ function (this: dgram.Socket, ...args: unknown[]) {
+ guardSocketHost(
+ typeof args[1] === "string" ? args[1] : "localhost",
+ "udp",
+ );
+ return originalDgramConnect.apply(this, args);
+ },
+ );
+
+ const originalUdpSocket = Bun.udpSocket as unknown as Callable;
+ replace(
+ Bun,
+ "udpSocket",
+ async (options: { connect?: { hostname?: string } }) => {
+ if (options?.connect)
+ guardSocketHost(options.connect.hostname ?? "", "udp");
+ const socket = (await originalUdpSocket(options)) as {
+ send: Callable;
+ sendMany: Callable;
+ };
+ const send = socket.send.bind(socket);
+ socket.send = (...args: unknown[]) => {
+ if (typeof args[2] === "string") guardSocketHost(args[2], "udp");
+ return send(...args);
+ };
+ const sendMany = socket.sendMany.bind(socket);
+ socket.sendMany = (packets: unknown) => {
+ if (!options?.connect && Array.isArray(packets)) {
+ for (let i = 2; i < packets.length; i += 3)
+ guardSocketHost(String(packets[i]), "udp");
+ }
+ return sendMany(packets);
+ };
+ return socket;
+ },
+ );
+
+ // Bun snapshots a built-in's named exports when it first loads; this re-reads every replacement
+ // above into them, so `import { get } from "node:https"` in a test file is the guarded one.
+ syncBuiltinESMExports();
+}
diff --git a/server/scripts/test-preload.ts b/server/scripts/test-preload.ts
index beb9760ee..aecde0a84 100644
--- a/server/scripts/test-preload.ts
+++ b/server/scripts/test-preload.ts
@@ -20,10 +20,15 @@
* `@copilotkit/runtime` also eagerly imports the Vertex provider, which pulls `gaxios` through a
* Bun global cache path during tests. These tests do not exercise Vertex, so the provider is stubbed
* at the same preload boundary and fails loudly if a server test tries to use it.
+ *
+ * And `./test-network-guard` closes the process's egress, for the same reason this file exists at
+ * all: it has to run before any test file is imported, because a vendor client binds its transport
+ * when it is constructed and a stub installed afterwards is one the client never sees.
*/
import { mock } from "bun:test";
import "eventsource";
+import "./test-network-guard";
mock.module("@ai-sdk/google-vertex", () => ({
createVertex: () => () => {
diff --git a/server/src/app.ts b/server/src/app.ts
index dcf69de23..b162f5dfa 100644
--- a/server/src/app.ts
+++ b/server/src/app.ts
@@ -99,6 +99,7 @@ import type { OnboardingStore } from "./people/onboarding";
import { MAX_PAGE, type PeopleStore } from "./people/store";
import type { AccountModeSwitch } from "./plugins/account-mode";
import type { ComposioBroker } from "./plugins/broker";
+import type { BrokerProvider } from "./plugins/access";
import { createPluginRoutes } from "./plugins/routes";
import { createSharedUseRoutes } from "./plugins/shared-use-routes";
import type { SharedUseStore } from "./plugins/shared-use-store";
@@ -386,7 +387,7 @@ export function createApp(
* configured, which is the correct degraded behaviour: a deployment with no Composio API key has
* no app directory to offer, rather than one that lists apps nobody can connect.
*/
- composio?: { broker: ComposioBroker },
+ composio?: { broker: ComposioBroker; provider: BrokerProvider },
/** Native model OAuth stays server-side; callers hold only a separate local bearer. */
modelProviderProxy?: ModelProviderProxy,
userPreferences?: UserPreferencesStore,
diff --git a/server/src/audit.ts b/server/src/audit.ts
index 356e0f0a8..dfc737734 100644
--- a/server/src/audit.ts
+++ b/server/src/audit.ts
@@ -235,6 +235,19 @@ export const auditEventTypes = [
* while the grant at Google stood untouched.
*/
"mcp.account_disconnected",
+ /*
+ * One person retracted their own pending per-action authorization before it completed.
+ *
+ * The counterpart of `mcp.account_connected` on the same lifecycle: that row is the attempt
+ * ending in a yes, this one is the person deliberately ending it themselves. Without it, pressing
+ * Cancel deleted the attempt and nothing recorded who withdrew what — on a lifecycle whose whole
+ * point is that consent is attributable. Filed under the app's slug, as its two siblings are.
+ *
+ * NEVER THE CONSENT URL AND NEVER THE VENDOR'S HANDLE FOR THE FLOW: the first is a bearer
+ * capability, and neither is in the payload. `minted` says only whether a flow had been started at
+ * the vendor at all, which is the one fact about it a reader of the trail needs.
+ */
+ "mcp.authorization_cancelled",
// An administrator switched a brokered app between each person's account and one shared account.
"mcp.account_mode_changed",
// A Bot's use of a Shared app needs an administrator's approval: filed by a refused call or by the
diff --git a/server/src/components/headless.ts b/server/src/components/headless.ts
index 9ef4f70ae..1b629101e 100644
--- a/server/src/components/headless.ts
+++ b/server/src/components/headless.ts
@@ -217,11 +217,15 @@ const ActivityReportProps = z.object({
),
});
-/** Which server-side function each activity report reads. The same map `activity.tsx` holds. */
-const ACTIVITY_FUNCTION_FOR: Record = {
- activity: "botActivity",
- refusals: "recentRefusals",
-};
+/**
+ * Which server-side function each activity report reads. The same map `activity.tsx` holds, and a
+ * Map there for the same reason: the key is a string somebody else chose, and an object lookup
+ * answers a borrowed `Object.prototype` member with something truthy that is no function name.
+ */
+const ACTIVITY_FUNCTION_FOR: ReadonlyMap = new Map([
+ ["activity", "botActivity"],
+ ["refusals", "recentRefusals"],
+]);
export type HeadlessComponentSpec = {
/** The tool name, the catalogue key, and the renderer's key in the app. */
@@ -315,7 +319,7 @@ export const HEADLESS_COMPONENTS: readonly HeadlessComponentSpec[] = [
reads: (args) => {
const functionName =
typeof args.report === "string"
- ? ACTIVITY_FUNCTION_FOR[args.report]
+ ? ACTIVITY_FUNCTION_FOR.get(args.report)
: undefined;
return functionName ? [functionName] : [];
},
diff --git a/server/src/config.ts b/server/src/config.ts
index 337100055..aa1409f5b 100644
--- a/server/src/config.ts
+++ b/server/src/config.ts
@@ -16,6 +16,7 @@ import {
type TranscriptionConfig,
transcriptionConfig,
} from "./dictation/config";
+import type { BrokerProvider } from "./plugins/access";
import { type VoiceConfig, voiceConfig } from "./voice/config";
import { type WebBotAuth, webBotAuthFromEnv } from "./web-bot-auth";
@@ -249,6 +250,16 @@ export type DeploymentConfig = {
composioApiKey: string | undefined;
/** GitHub App user sign-in, so people can link their GitHub account. Both or neither of GITHUB_APP_CLIENT_ID / GITHUB_APP_CLIENT_SECRET. */
githubApp?: GithubAppConfig;
+ /**
+ * The Arcade API key, from `ARCADE_API_KEY`. Unset is the ordinary state. Trimmed and never
+ * validated here, for the same reason as `composioApiKey`.
+ */
+ arcadeApiKey: string | undefined;
+ /**
+ * Which tool broker the operator asked for, from `OPENBOT_BROKER_PROVIDER`. Only read and
+ * validated here: which provider is active is decided elsewhere. Unset is `undefined`.
+ */
+ brokerProvider: BrokerProvider | undefined;
/**
* Where this deployment is reached from outside, with no trailing slash.
*
@@ -421,6 +432,17 @@ function optional(environment: Environment, name: string): string | undefined {
return environment[name]?.trim() || undefined;
}
+function brokerProvider(environment: Environment): BrokerProvider | undefined {
+ const raw = optional(environment, "OPENBOT_BROKER_PROVIDER");
+ if (raw === undefined) return undefined;
+ if (raw !== "composio" && raw !== "arcade") {
+ throw new Error(
+ `OPENBOT_BROKER_PROVIDER must be "composio" or "arcade", got "${raw}". Leave it unset to choose by which API key is configured.`,
+ );
+ }
+ return raw;
+}
+
/**
* Whether this deployment says it is in production, which is what the two hard refusals turn on.
*
@@ -1353,6 +1375,8 @@ export function loadConfig(
agentEndpointAllowedHosts: agentEndpointAllowedHosts(environment),
deploymentId: optional(environment, "DEPLOYMENT_ID"),
composioApiKey: optional(environment, "COMPOSIO_API_KEY"),
+ arcadeApiKey: optional(environment, "ARCADE_API_KEY"),
+ brokerProvider: brokerProvider(environment),
publicUrl,
appUrl: (
optional(environment, "OPENBOT_APP_URL") ??
diff --git a/server/src/db/apply-migrations.ts b/server/src/db/apply-migrations.ts
new file mode 100644
index 000000000..5e32a61c7
--- /dev/null
+++ b/server/src/db/apply-migrations.ts
@@ -0,0 +1,162 @@
+/**
+ * Apply a migration folder to a database, or refuse before touching it.
+ *
+ * Separate from `scripts/migrate.ts` so that the refusal has a test. The script is an executable —
+ * it reads the environment and exits — and the only way to find out what it does to a database that
+ * is one journal out of step is to run it against such a database, which is what
+ * `tests/migration-reach.integration.test.ts` does through this function.
+ *
+ * See `./migration-reach` for what is being checked and why the answer is a refusal rather than a
+ * repair.
+ */
+import { drizzle } from "drizzle-orm/postgres-js";
+import { migrate } from "drizzle-orm/postgres-js/migrator";
+import type { Sql, TransactionSql } from "postgres";
+import {
+ type AppliedMigration,
+ type MigrationsJournal,
+ migrationsOutOfReach,
+ outOfReachRefusal,
+ readMigrationFolder,
+} from "./migration-reach";
+
+/**
+ * Where the migrator keeps its bookkeeping, named once.
+ *
+ * `migrate()` writes to whatever `migrationsSchema` / `migrationsTable` it is handed, and the
+ * reach check below reads the same table to decide whether migrating is safe. If the two ever named
+ * different tables the check would read an absent table, see nothing applied, and wave every
+ * migration through — the refusal silently switched off. So both are handed this one object, and
+ * nothing else in the server spells the name.
+ *
+ * The same object reaches the refusal's repair SQL (`outOfReachRefusal`), so the table an operator
+ * is told to fix is the table the check read. The type is declared in `./migration-reach` and the
+ * value here: that module is the dependency, not the dependent, and importing this value into it
+ * would close a cycle.
+ */
+export type { MigrationsJournal };
+
+export const MIGRATIONS_JOURNAL: MigrationsJournal = {
+ migrationsSchema: "drizzle",
+ migrationsTable: "__drizzle_migrations",
+};
+
+/**
+ * What the migrator has already recorded here, or nothing at all.
+ *
+ * `to_regclass` rather than a query that may fail: a database the migrator has never touched has no
+ * journal schema, and selecting from a table that is not there would be an error indistinguishable
+ * from a database that cannot be read.
+ */
+async function appliedMigrations(
+ client: TransactionSql,
+ { migrationsSchema, migrationsTable }: MigrationsJournal,
+): Promise {
+ const [present] = await client<{ table: string | null }[]>`
+ select to_regclass(format('%I.%I', ${migrationsSchema}::text, ${migrationsTable}::text))::text as table
+ `;
+ if (!present?.table) return [];
+
+ const rows = await client<{ hash: string; created_at: string | null }[]>`
+ select hash, created_at from ${client(migrationsSchema)}.${client(migrationsTable)}
+ `;
+ return rows.map((row) => ({
+ hash: row.hash,
+ /*
+ * A NULL `created_at` is CARRIED, never coerced. `Number(null)` is 0, and 0 is a legitimate
+ * stamp — so coercing here would hand `migratorHighWaterMark` a row indistinguishable from an
+ * early migration and the guard would pass on exactly the database it exists to refuse. The
+ * column is a bare `bigint` drizzle leaves nullable; see `./migration-reach`.
+ */
+ createdAt: row.created_at === null ? null : Number(row.created_at),
+ }));
+}
+
+/** Thrown instead of migrating, when migrating would skip work and call it done. */
+export class MigrationsOutOfReachError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "MigrationsOutOfReachError";
+ }
+}
+
+/**
+ * One lock per database for every run of this function, whatever journal it carries.
+ *
+ * A constant rather than anything derived from the folder: the runs that must not overlap are
+ * precisely the ones whose folders differ. Advisory locks are scoped to the database, so two
+ * databases on one server never wait on each other.
+ */
+const MIGRATION_LOCK = "openbot:apply-migrations";
+
+/**
+ * Check, then migrate, with nothing able to commit in between.
+ *
+ * THE GAP THIS CLOSES. The reach check reads the journal table; the migrator then reads it AGAIN to
+ * find its high-water mark. Run as two steps, a second migrator could commit between
+ * those reads — and if it carries a different journal (an operator's `db:migrate` from a checkout,
+ * or a second release's Job started while the first is still running) its stamp can land above a
+ * migration this run still has pending. The check passed before that stamp existed; the migrator
+ * reads it afterwards, walks past the migration, and the run reports success over a table that is
+ * not there: the exact outcome the check exists to prevent, arrived at through the gap between the
+ * two reads. A rival with the SAME journal (a Job pod the controller started twice) is the lesser
+ * case: both read the same mark, both run the same SQL, and one dies on a duplicate `CREATE`.
+ *
+ * So both run inside one transaction that first takes a transaction-scoped advisory lock. Every
+ * other run of this function queues behind it and, once admitted, checks a table the previous run
+ * has already committed. The lock goes with the transaction — on commit, on the refusal's rollback,
+ * or when the connection dies — so there is no unlock to forget and no session to leak it into.
+ *
+ * WHAT IT DOES NOT COVER. A migrator that is not this function (`drizzle-kit migrate`, or an image
+ * built before this lock) does not take it, and can still interleave. Every path this repository
+ * runs goes through here; see `scripts/migrate.ts`.
+ */
+export async function applyMigrations(
+ client: Sql,
+ migrationsFolder: string,
+ journal: MigrationsJournal = MIGRATIONS_JOURNAL,
+): Promise {
+ const folder = await readMigrationFolder(migrationsFolder);
+
+ await client.begin(async (tx) => {
+ await tx`select pg_advisory_xact_lock(hashtextextended(${MIGRATION_LOCK}, 0))`;
+
+ const applied = await appliedMigrations(tx, journal);
+ const outOfReach = migrationsOutOfReach(folder, applied);
+ if (outOfReach.length > 0) {
+ throw new MigrationsOutOfReachError(
+ outOfReachRefusal(outOfReach, folder, applied, journal),
+ );
+ }
+
+ await migrate(drizzle(withinTransaction(client, tx)), {
+ migrationsFolder,
+ ...journal,
+ });
+ });
+}
+
+/**
+ * The open transaction, in the shape `drizzle()` takes a client in.
+ *
+ * Drizzle's migrator reads its high-water mark and runs the migrations through the client it is
+ * given, and opens its own transaction with `client.begin`. A postgres.js transaction has no
+ * `begin` — only `savepoint` — and no `options`, which `drizzle()` writes its type parsers into. So
+ * this hands it exactly those three things: `unsafe` and `savepoint` from the transaction, so every
+ * statement the migrator sends is inside the lock, and `options` from the pool the transaction
+ * belongs to, which is where postgres.js reads parsers from. Its transaction becomes a savepoint in
+ * ours, with the same all-or-nothing effect it always had.
+ *
+ * If a later drizzle-orm reaches for anything else on its client, it fails here with a TypeError at
+ * the first migration rather than migrating outside the lock — and
+ * `tests/migration-reach.integration.test.ts` runs exactly this path.
+ */
+function withinTransaction(client: Sql, tx: TransactionSql): Sql {
+ const adapter = {
+ options: client.options,
+ unsafe: tx.unsafe.bind(tx),
+ begin: (fn: (sql: TransactionSql) => T | Promise) => tx.savepoint(fn),
+ };
+ // The cast is the claim above: drizzle uses these three members and no others.
+ return adapter as unknown as Sql;
+}
diff --git a/server/src/db/migration-reach.ts b/server/src/db/migration-reach.ts
new file mode 100644
index 000000000..ff813cbe5
--- /dev/null
+++ b/server/src/db/migration-reach.ts
@@ -0,0 +1,243 @@
+/**
+ * Whether every migration in the folder can still be reached by the migrator, on THIS database.
+ *
+ * WHAT THE MIGRATOR ACTUALLY DOES. `PgDialect.migrate` reads one row of its journal table (see
+ * `MigrationsJournal`) — `order by created_at desc limit 1` — and then runs a migration
+ * if, and only if, its journal `when` is greater than `Number(created_at)` of that row. That row is
+ * not always the newest stamp: Postgres sorts NULLs FIRST in a descending order, so a single row
+ * with a NULL `created_at` is the row it reads, and `Number(null)` is 0. See `migratorHighWaterMark`. It never looks at hashes, never looks at tags, and never notices that a
+ * migration it walked past was one it has never run. A migration whose `when` is at or below the
+ * high-water mark is therefore not "pending": it is unreachable, forever, and the migrator will
+ * report success without it.
+ *
+ * WHY THAT IS NOT HYPOTHETICAL. A branch that ships migrations and is then merged with `main` has
+ * its own migrations renumbered and restamped to sit after main's. Every database that ran the
+ * branch BEFORE the merge has already recorded the branch's old, higher stamp. Main's migrations
+ * now carry a `when` older than that mark, so they are skipped — silently — while the renumbered
+ * branch migrations, stamped later still, run perfectly. The database ends up missing tables that
+ * the migrator just said it had applied. A fresh database is unaffected, which is exactly why CI,
+ * which always migrates from empty, cannot see it.
+ *
+ * WHY THIS REFUSES RATHER THAN REPAIRS. Running the unreachable SQL and backfilling its row is a
+ * two-line change and the wrong one. The migrations that are out of reach are out of reach because
+ * some OTHER journal already ran against this database, and nothing here knows what that journal
+ * did — whether its statements created the same objects under other names, left half a schema
+ * behind, or were the hand-repair of an earlier failure. The honest answer to "this database cannot
+ * be brought up to date by running the folder forward" is to say so, loudly, with the names in
+ * hand, and to stop. An operator can then drop a disposable database, or repair a durable one
+ * deliberately. What must never happen again is `{"type":"migrations-applied","status":"ok"}` over
+ * tables that do not exist.
+ */
+import { createHash } from "node:crypto";
+import { readFile } from "node:fs/promises";
+import { join } from "node:path";
+
+/** One migration in the folder, as the migrator reads it. */
+export type FolderMigration = {
+ tag: string;
+ /** The journal's `when`: the only thing the migrator compares. */
+ when: number;
+ /**
+ * sha256 of the whole `.sql` file, hex.
+ *
+ * Computed exactly as `drizzle-orm/migrator`'s `readMigrationFiles` computes it — the whole file
+ * as read, before any statement splitting — because this is matched against the hashes that the
+ * migrator itself wrote into its journal table. A different recipe here would make
+ * every applied migration look unapplied.
+ */
+ hash: string;
+};
+
+/**
+ * One row of the migrator's journal table (see `MigrationsJournal`).
+ *
+ * `createdAt` is nullable because the column is: drizzle creates it as a bare `bigint`, and a NULL
+ * there is not noise to be defaulted away — it is the row the migrator compares against. A reader
+ * that turns NULL into a number before it gets here hides exactly the case that matters.
+ */
+export type AppliedMigration = { hash: string; createdAt: number | null };
+
+/**
+ * Where the migrator keeps its bookkeeping: the table it reads its high-water mark from and records
+ * each applied migration in.
+ *
+ * The SHAPE lives here, in the module with no database in it, and the VALUE does not:
+ * `MIGRATIONS_JOURNAL` in `./apply-migrations` is the one place the name is chosen, and it is handed
+ * to everything that needs it — the reach check's read, `migrate()`, and `outOfReachRefusal`'s repair
+ * instructions. The value cannot move here without this module deciding deployment configuration,
+ * and it cannot be imported from there without a cycle (`./apply-migrations` imports this module),
+ * so it is passed in. Nothing in this file may spell the table's name.
+ */
+export type MigrationsJournal = {
+ migrationsSchema: string;
+ migrationsTable: string;
+};
+
+/**
+ * The journal table as an operator would type it into `psql`: schema-qualified, each part quoted.
+ *
+ * Quoted always rather than when needed, because "when needed" is a reserved-word list this file
+ * has no business keeping; a quoted lower-case identifier means exactly what the bare one does.
+ */
+function journalTable({
+ migrationsSchema,
+ migrationsTable,
+}: MigrationsJournal): string {
+ const quote = (identifier: string) => `"${identifier.replaceAll('"', '""')}"`;
+ return `${quote(migrationsSchema)}.${quote(migrationsTable)}`;
+}
+
+type Journal = { entries: { idx: number; tag: string; when: number }[] };
+
+/**
+ * The folder, read the way the migrator reads it: the journal decides, not the directory listing.
+ *
+ * A `.sql` file with no journal entry never runs and is not this function's business; an entry with
+ * no file stops the migrator dead, and `readFile` rejecting here says so in the same breath.
+ */
+export async function readMigrationFolder(
+ folder: string,
+): Promise {
+ const journal = JSON.parse(
+ await readFile(join(folder, "meta", "_journal.json"), "utf8"),
+ ) as Journal;
+
+ return Promise.all(
+ journal.entries.map(async (entry) => ({
+ tag: entry.tag,
+ when: entry.when,
+ hash: createHash("sha256")
+ .update(await readFile(join(folder, `${entry.tag}.sql`), "utf8"))
+ .digest("hex"),
+ })),
+ );
+}
+
+/**
+ * The number the migrator will compare every journal `when` against, chosen the way it chooses it.
+ *
+ * `PgDialect.migrate` (drizzle-orm 0.45.2) runs `select … order by created_at desc limit 1` and then
+ * `Number(lastDbMigration.created_at) < migration.folderMillis`. Two things follow, and this
+ * function must reproduce both, because a guard that picks a different row than the migrator is a
+ * guard about some other migrator:
+ *
+ * - Postgres puts NULLs FIRST in a descending sort, so one NULL `created_at` is the row it reads,
+ * however many real stamps sit below it. `Math.max` over the stamps picks the largest real one
+ * and disagrees.
+ * - `Number(null)` is 0, so that row's mark is 0 and every migration in the folder is "after" it:
+ * the migrator re-runs the whole history and dies on the first `CREATE TYPE` that already exists.
+ *
+ * `undefined` for an empty table: the migrator then has no row and runs everything, correctly.
+ */
+export function migratorHighWaterMark(
+ applied: readonly AppliedMigration[],
+): number | undefined {
+ if (applied.length === 0) return undefined;
+ if (applied.some((row) => row.createdAt === null)) return 0;
+ return Math.max(...applied.map((row) => row.createdAt ?? 0));
+}
+
+/**
+ * The migrations the migrator would get wrong on this database, judged against the row it reads.
+ *
+ * Two ways, both of which end with the migrator's word and the database disagreeing:
+ *
+ * - OUT OF REACH: never applied, and stamped at or below the mark — walked past forever, while the
+ * run reports success.
+ * - TO BE RE-RUN: already applied (its hash is recorded), yet stamped ABOVE the mark — run a second
+ * time. With honest stamps the mark is at least every applied migration's `when`, so this is
+ * empty; it fills when the row the migrator reads is not the newest stamp, which a NULL
+ * `created_at` guarantees.
+ *
+ * An empty `applied` is a database the migrator has never touched, where everything is reachable by
+ * definition — a fresh database can never be in this trouble, and saying so explicitly keeps the
+ * check from having an opinion about the one case it has no evidence for.
+ */
+export function migrationsOutOfReach(
+ folder: readonly FolderMigration[],
+ applied: readonly AppliedMigration[],
+): FolderMigration[] {
+ const highWaterMark = migratorHighWaterMark(applied);
+ if (highWaterMark === undefined) return [];
+
+ const appliedHashes = new Set(applied.map((row) => row.hash));
+
+ return folder.filter((migration) =>
+ migration.when <= highWaterMark
+ ? !appliedHashes.has(migration.hash)
+ : appliedHashes.has(migration.hash),
+ );
+}
+
+/**
+ * The refusal, written for whoever is reading a failed deploy at an unreasonable hour.
+ *
+ * It names the mark and what set it, every migration that cannot be reached, and the two ways out,
+ * because the one thing an operator cannot work out from a stack trace is that the database is
+ * *ahead* of the folder rather than behind it.
+ *
+ * `journal` is required, not defaulted: the repair instructions are SQL an operator will paste, and
+ * they must name the table the check actually read. A default here would be a second spelling of the
+ * name, and the first configuration that moved the journal would make this message send someone to
+ * repair a table that is not the one refusing them.
+ */
+export function outOfReachRefusal(
+ outOfReach: readonly FolderMigration[],
+ folder: readonly FolderMigration[],
+ applied: readonly AppliedMigration[],
+ journal: MigrationsJournal,
+): string {
+ const table = journalTable(journal);
+ const highWaterMark = migratorHighWaterMark(applied) ?? 0;
+ const appliedHashes = new Set(applied.map((row) => row.hash));
+ const skipped = outOfReach.filter(
+ (migration) => !appliedHashes.has(migration.hash),
+ );
+ const rerun = outOfReach.filter((migration) =>
+ appliedHashes.has(migration.hash),
+ );
+ const nulls = applied.filter((row) => row.createdAt === null).length;
+ const inFolder = new Set(folder.map((migration) => migration.hash));
+ /*
+ * Rows this folder cannot account for. Not a failure on its own — a squashed or deleted migration
+ * leaves them too — but when migrations are out of reach these rows are almost always the reason,
+ * so the count is the fastest thing an operator can act on.
+ */
+ const unknown = applied.filter((row) => !inFolder.has(row.hash)).length;
+ const mark =
+ nulls > 0
+ ? "a row with no created_at, which the migrator reads first and counts as 0"
+ : (folder.find((migration) => migration.when === highWaterMark)?.tag ??
+ "a migration this folder does not contain");
+ const list = (migrations: readonly FolderMigration[]) =>
+ migrations.map(
+ (migration) => ` - ${migration.tag} (stamped ${migration.when})`,
+ );
+
+ return [
+ `This database's migration journal, ${table}, has already recorded a migration stamped ${highWaterMark} (${mark}), and the migrator only runs migrations stamped after the row it reads: the first one ordered by created_at descending.`,
+ ...(nulls === 0
+ ? []
+ : [
+ `${nulls === 1 ? "1 applied row has" : `${nulls} applied rows have`} a NULL created_at. Postgres sorts NULLs first in a descending order, so that row, not the newest stamp, is the mark.`,
+ ]),
+ ...(skipped.length === 0
+ ? []
+ : [
+ `${skipped.length === 1 ? "This migration is" : `These ${skipped.length} migrations are`} stamped at or below that mark and ${skipped.length === 1 ? "has" : "have"} never been applied here, so running the migrator would skip ${skipped.length === 1 ? "it" : "them"} and report success over the schema ${skipped.length === 1 ? "it" : "they"} should have created:`,
+ ...list(skipped),
+ ]),
+ ...(rerun.length === 0
+ ? []
+ : [
+ `${rerun.length === 1 ? "This migration has" : `These ${rerun.length} migrations have`} already been applied here but ${rerun.length === 1 ? "is" : "are"} stamped above that mark, so running the migrator would run ${rerun.length === 1 ? "it" : "them"} a second time:`,
+ ...list(rerun),
+ ]),
+ unknown === 0
+ ? "Every applied migration is still present in this folder."
+ : `${unknown} applied ${unknown === 1 ? "migration is" : "migrations are"} not in this folder at all, which is what a renumbered or rewritten migration history looks like from here: this database ran a journal that no longer exists.`,
+ nulls > 0
+ ? `Nothing has been applied. Give every row with a NULL created_at the journal \`when\` of the migration whose hash it records (\`update ${table} set created_at = where hash = \`), or delete a row that matches no migration, and run again.`
+ : `Nothing has been applied. Recreate the database from empty if it is disposable (a development, preview or CI database usually is); otherwise apply the SQL above by hand and record each one with \`insert into ${table} (hash, created_at) values (, )\`, which leaves the high-water mark where it is.`,
+ ].join("\n");
+}
diff --git a/server/src/db/schema/plugins.ts b/server/src/db/schema/plugins.ts
index 7d2d7d2e0..83da06c4e 100644
--- a/server/src/db/schema/plugins.ts
+++ b/server/src/db/schema/plugins.ts
@@ -166,6 +166,34 @@ export const mcpTools = pgTable(
.notNull()
.references(() => mcpServers.id, { onDelete: "cascade" }),
name: text("name").notNull(),
+ /**
+ * THE VENDOR'S OWN SPELLING OF THIS ACTION, which is not always a name a model may be offered.
+ *
+ * `name` is this deployment's identifier for the action: half of the `/` ref a
+ * grant is written against, and the half of `mcp____` a model is offered. A
+ * model tool name must match `^[a-zA-Z0-9_-]+$`, so `name` must too.
+ *
+ * THE TWO USED TO BE ONE COLUMN, AND ARCADE IS WHERE THAT BROKE. Its listing answers
+ * `qualified_name` — `Gmail.ListEmails` — and the dot is legal at the vendor and illegal in a
+ * tool name, so every Arcade action this deployment recorded produced an offered name the model
+ * API rejects outright. Not the one tool: the request carries every tool together, so a single
+ * granted Arcade action failed the whole call with `Invalid 'tools[47].name'`, and no Arcade
+ * tool had ever been callable by any model.
+ *
+ * STORED RATHER THAN RECONSTRUCTED. `Gmail.ListEmails` is `.` and the toolkit
+ * is already the server id, so the vendor's spelling LOOKS derivable from `name` — but that is a
+ * rule about the shape of today's listing, not a promise the vendor makes, and the one thing
+ * that must never be guessed is the string an action is executed by. It is also what the
+ * per-action authorization flow speaks end to end (`grantsFor`, the Authorize button, the
+ * attempt row, `?action=`, the vendor's own `authorize({ action })`) and what this deployment's
+ * refusal sentences quote back, so it is a fact the row has to hold.
+ *
+ * EQUAL TO `name` FOR EVERY OTHER TRANSPORT, and that is not a placeholder. Composio's
+ * `GMAIL_FETCH_EMAILS` and an MCP server's names are already offerable as they stand, so there
+ * are no two spellings to keep apart and the column says so. Migration 0063 backfilled it that
+ * way for the same reason.
+ */
+ vendorName: text("vendor_name").notNull(),
description: text("description").notNull().default(""),
/** The tool's own JSON Schema, passed to the model unchanged. */
inputSchema: jsonb("input_schema").notNull().default({}),
@@ -410,7 +438,7 @@ export const composioConnections = pgTable(
export const brokeredConnections = pgTable(
"brokered_connections",
{
- provider: text("provider").$type<"composio">().notNull(),
+ provider: text("provider").$type<"composio" | "arcade">().notNull(),
app: text("app").notNull(),
holder: text("holder").$type<"person" | "deployment">().notNull(),
userId: text("user_id"),
@@ -431,7 +459,7 @@ export const brokeredConnections = pgTable(
),
check(
"brokered_connections_provider_check",
- sql`${table.provider} IN ('composio')`,
+ sql`${table.provider} IN ('composio', 'arcade')`,
),
uniqueIndex("brokered_connections_person_idx")
.on(table.provider, table.app, table.userId)
@@ -443,6 +471,138 @@ export const brokeredConnections = pgTable(
],
);
+/**
+ * One in-flight Arcade authorization: what the returning browser is verified against.
+ *
+ * `actor_id` IS THE SIGNED-IN PERSON'S `users.id` — the session subject — AND NEVER
+ * `credentialActorId`. `store.ts` reads `input.credentialActorId ?? input.actorId`, and on a Team
+ * Bot those differ, and neither is the person holding the browser. The verifier finds this attempt
+ * from the returning browser's session, so any other key writes attempts a browser can never match.
+ *
+ * The key is the actor alone, so a person has at most one attempt at a time. A PRIMARY KEY with
+ * "expired is treated as absent" would be a permanent 409, because the expired row still occupies
+ * the key. Starting an attempt is therefore one `INSERT ... ON CONFLICT (actor_id) DO UPDATE ...
+ * WHERE created_at < now() - interval '15 minutes' OR completed_at IS NOT NULL`: a live attempt is
+ * refused, an expired or completed one is taken over, and nobody is ever locked out.
+ *
+ * `completed_at` is what makes completion happen exactly once: the verifier claims the row by
+ * setting it, and a second return finds it set. `auth_id` is what binds a callback to this attempt;
+ * an attempt holding null can never complete.
+ *
+ * THE ROW IS THE ACTOR'S, AND ONLY `actor_id` CARRIES THE KEY TO `users`. `actor_id` is `users.id`,
+ * and a column that names a table without a key is a column the database will let drift: a deleted
+ * person left attempt rows pointing at nobody, and the verifier's "find the attempt for this
+ * session" would then be matching a key no session can ever present.
+ *
+ * `user_id` IS NOT A SECOND PERSON. It is whose account the attempt is for, and for a `person`
+ * attempt that is the actor and nobody else — `accountRefFor` answers a Personal app with
+ * `{ userId: actorId }`, and nothing lets one person consent into another's account. So the database
+ * holds `user_id IS NULL OR user_id = actor_id` (`pending_authorizations_user_is_actor_check`), and
+ * through that CHECK `user_id` names a person exactly when `actor_id` does. It carried a cascading
+ * key of its own from `0060` to `0062`, and with nothing making the two columns equal that key let a
+ * row name two people and die with EITHER: deleting one person took an attempt a DIFFERENT person
+ * had started. An attempt is evidence of somebody's consent in progress, and the one person whose
+ * deletion may end it is the one who started it.
+ *
+ * CASCADE RATHER THAN RESTRICT, AND THIS ROW IS NOT THE AUDIT TRAIL. An attempt is in-flight
+ * working state with a fifteen-minute life and at most one row per actor, holding no grant and no
+ * secret; what a person authorized is recorded in `audit_events`, which deliberately carries
+ * NO key to `users` for exactly the reason the trail must outlive them. `RESTRICT` here would make
+ * a person undeletable for fifteen minutes because they happened to have a browser mid-redirect,
+ * which is a legitimate deletion blocked by ephemeral state. Note the contrast with
+ * {@link brokeredConnections}, whose `user_id` is keyless ON PURPOSE: that row IS the record of
+ * which apps somebody held, and `retireConnectionsFor` reads it AFTER the `users` row is gone.
+ * Nothing reads an attempt after its person is gone.
+ *
+ * `server_id`'s key gets an index of its own. A foreign key is not indexed by postgres on the
+ * referencing side, so deleting one `mcp_servers` row made the cascade scan this table, and so did
+ * every lookup by server. `actor_id` is the primary key and indexed by it; `user_id`, no longer a
+ * key and read by nothing, has none.
+ */
+export const pendingAuthorizations = pgTable(
+ "pending_authorizations",
+ {
+ actorId: text("actor_id")
+ .primaryKey()
+ .references(() => users.id, { onDelete: "cascade" }),
+ serverId: text("server_id")
+ .notNull()
+ .references(() => mcpServers.id, { onDelete: "cascade" }),
+ action: text("action").notNull(),
+ /**
+ * Whose account this attempt is for, with the same meaning and the same two values as
+ * {@link brokeredConnections.holder} — and held by the same CHECK, because `$type<>` is erased
+ * at runtime and the row this attempt is reconciled into is held to that pair.
+ */
+ holder: text("holder").$type<"person" | "deployment">().notNull(),
+ /** Always the actor or `null`; see `pending_authorizations_user_is_actor_check` below. */
+ userId: text("user_id"),
+ vendorUserId: text("vendor_user_id").notNull(),
+ authId: text("auth_id"),
+ createdAt: createdAt(),
+ completedAt: timestamp("completed_at", { withTimezone: true }),
+ /**
+ * How the attempt ended, `null` while it is still in flight.
+ *
+ * The four names are a ROSTER the database holds, not a comment: every reader switches on them,
+ * and a fifth value written by hand — or by a caller that cast past the declared union — would
+ * reach a `switch` the compiler has already been told is exhaustive.
+ */
+ outcome: text("outcome").$type<
+ "completed" | "failed" | "unreconciled" | "withdrawn"
+ >(),
+ },
+ (table) => [
+ check(
+ "pending_authorizations_holder_check",
+ sql`(${table.holder} = 'person' AND ${table.userId} IS NOT NULL) OR (${table.holder} = 'deployment' AND ${table.userId} IS NULL)`,
+ ),
+ check(
+ "pending_authorizations_outcome_check",
+ sql`${table.outcome} IS NULL OR ${table.outcome} IN ('completed', 'failed', 'unreconciled', 'withdrawn')`,
+ ),
+ /**
+ * An attempt has ended, or it has not: `completed_at` and `outcome` are set together or
+ * neither is.
+ *
+ * A ROW CARRYING AN `outcome` WITH `completed_at` STILL NULL IS A FINISHED ATTEMPT THAT EVERY
+ * READER READS AS A LIVE ONE. `claimAuthorizationAttempt` frees a held key only
+ * `WHERE created_at < now() - interval '15 minutes' OR completed_at IS NOT NULL`, and
+ * `authorizationAttemptFor` answers on `completed_at IS NULL` alone; neither consults
+ * `outcome`. So one such row locks its actor out of authorizing anything for the rest of the
+ * 15-minute TTL, with a verdict already recorded against it. The mirror shape — a
+ * `completed_at` with no `outcome` — is an attempt that ended with nothing saying how, and
+ * `outcome` is what every operator-facing reading of this table is of.
+ *
+ * HERE AND NOT IN THE APPLICATION, although both writers already set the pair in one
+ * statement. That those two agree today is precisely what makes the invariant invisible to the
+ * next writer, to a hand-run UPDATE and to a partial restore, and none of those three passes
+ * through the two functions that currently get it right. Written as a boolean equality rather
+ * than two `OR`-ed clauses because `(x IS NULL) = (y IS NULL)` is never itself NULL, so the
+ * CHECK can only pass or refuse — it has no third, silently-accepting verdict.
+ */
+ check(
+ "pending_authorizations_completion_check",
+ sql`(${table.completedAt} IS NULL) = (${table.outcome} IS NULL)`,
+ ),
+ /**
+ * A `person` attempt is for the actor's own account, so the row names one person, not two.
+ *
+ * HERE AND NOT ONLY IN `accountRefFor`, because this is what keeps a cascade on `users` from
+ * reaching a row somebody else started — the guarantee is the database's or it is nobody's: a
+ * hand-run INSERT, a restore, or the next writer does not pass through `accountRefFor`. The
+ * `IS NULL` arm is the `deployment` attempt, whose shape `pending_authorizations_holder_check`
+ * already decides; spelled out so this CHECK says what it admits rather than leaning on a NULL
+ * comparison passing.
+ */
+ check(
+ "pending_authorizations_user_is_actor_check",
+ sql`${table.userId} IS NULL OR ${table.userId} = ${table.actorId}`,
+ ),
+ index("pending_authorizations_server_idx").on(table.serverId),
+ ],
+);
+
/**
* One person's grant on one MCP server: the row that makes a Bot answer as the asker.
*
diff --git a/server/src/index.ts b/server/src/index.ts
index 1a519b93c..225af16a3 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -6,7 +6,7 @@ import {
import { COMPUTER_GUIDANCE } from "@openbot/shared/bot-prompt";
import { workOwner } from "@openbot/shared/work-owner";
import { createTurnRunner } from "@openbot/toolkit/run-turn";
-import { and, eq, isNull } from "drizzle-orm";
+import { and, eq, isNull, ne, sql } from "drizzle-orm";
import { z } from "zod";
import { createActivityStore } from "./activity/activity";
import {
@@ -130,6 +130,7 @@ import {
} from "./credentials";
import { createDatabase } from "./db/client";
import {
+ brokeredConnections,
channelAgents,
channelMemberships,
channels,
@@ -185,15 +186,23 @@ import { createPasswordStore } from "./passwords/store";
import { createOnboardingStore } from "./people/onboarding";
import { createPeopleStore } from "./people/store";
import { createAccountModeSwitch } from "./plugins/account-mode";
+import { assertActivatable, providerDecision } from "./plugins/broker";
+import { BROKERED_PROFILE } from "./plugins/access";
import { useRoutineTools } from "./plugins/builtin-routines";
+import { useArcadeClient } from "./plugins/arcade";
+import { createArcadeClient } from "./plugins/arcade-adapter";
import { useComposioClient } from "./plugins/composio";
import { createComposioClient } from "./plugins/composio-adapter";
-import { backfillComposioLogos } from "./plugins/logos";
+import { backfillBrokeredLogos } from "./plugins/logos";
import { redirectUriFor } from "./plugins/oauth";
import {
createSharedUseGate,
createSharedUseStore,
} from "./plugins/shared-use-store";
+import {
+ selectProviderWiring,
+ storedProvider,
+} from "./plugins/provider-wiring";
import { createPluginStore } from "./plugins/store";
import {
grantedSkills,
@@ -769,10 +778,35 @@ const sandboxedStore = createSandboxedStore(database, bootAuditStore);
* Installing a client built from an absent key would turn all of that into a vendor error at first
* use, which sends an operator looking for a broken Composio instead of at their own configuration.
*/
-const composio = config.composioApiKey
- ? createComposioClient(config.composioApiKey)
- : null;
-if (composio) useComposioClient(composio.actions);
+/*
+ * EXACTLY ONE PROVIDER, DECIDED ONCE. `selectProviderWiring` answers which transport client is
+ * installed and which broker goes on to the store and the routes; the other client is installed as
+ * an explicit `null`, so a deployment holding both keys (legal, and needed to end accounts at the
+ * old vendor) never has two seams live. It refuses, rather than wiring nothing, both configurations
+ * it cannot decide: a named provider whose key is unset, and both keys set with nothing named.
+ */
+const providerWiring = selectProviderWiring(config, {
+ composio: createComposioClient,
+ arcade: createArcadeClient,
+});
+useComposioClient(providerWiring.install.composio);
+useArcadeClient(providerWiring.install.arcade);
+const wiredProvider = providerWiring.provider;
+const composio =
+ wiredProvider && providerWiring.broker
+ ? { broker: providerWiring.broker, provider: wiredProvider }
+ : null;
+/*
+ * Activation is refused while the OTHER provider still holds connection rows. Keyed on rows, never
+ * on keys: having both keys set is the expected state while a sunset is in progress.
+ */
+if (wiredProvider) {
+ const [{ count: otherProviderRows }] = await database
+ .select({ count: sql`count(*)::int` })
+ .from(brokeredConnections)
+ .where(ne(brokeredConnections.provider, wiredProvider));
+ assertActivatable(wiredProvider, otherProviderRows);
+}
/**
* The private sign-in form and the Passwords vault. A Bot asks to be signed in; the person answers
@@ -835,7 +869,41 @@ const signInService = computerGateway
* module scope because the publication, assignment and grant routes below also need to reapprove
* or refile against it when a Bot's exposure changes.
*/
-const sharedUseStore = createSharedUseStore(database);
+/*
+ * WHICH SLICE of `brokered_connections` this deployment reads, decided ONCE, at the one boundary
+ * that can see both the configuration and the rows.
+ *
+ * THERE IS NO `?? "composio"` HERE ANY MORE, AND THAT WAS A BUG RATHER THAN A SHORTHAND.
+ * Configuration names no provider for three different reasons, and the one that matters is "both
+ * keys set, OPENBOT_BROKER_PROVIDER unset" — legal, documented, and exactly what a deployment
+ * migrating off Composio sits in while it ends its old accounts. Defaulting it to `composio` gave
+ * the plugin store and the shared-use store a filter that excludes every Arcade row this deployment
+ * holds, so live Arcade connections read as absent and the accounts behind them could not be
+ * revoked. `storedProvider` takes the three causes apart and, where configuration is silent, lets
+ * the rows name themselves. The shared-use store and the plugin store are both given this one
+ * value, so the gate and the approvals it reads agree.
+ */
+const providerConfiguration = providerDecision(config);
+const brokerProvider = storedProvider(
+ providerConfiguration,
+ (
+ await database
+ .selectDistinct({ provider: brokeredConnections.provider })
+ .from(brokeredConnections)
+ ).map((row) => row.provider),
+);
+if (providerConfiguration.kind === "ambiguous") {
+ /*
+ * Said out loud, once, at startup: this deployment has two keys and no answer, so nothing is
+ * active and nothing can be connected or revoked until one is named. The rows it already holds
+ * are read as their own provider's (above), which is why this is a warning and not a refusal —
+ * the screens stay honest, the vendor calls stay impossible.
+ */
+ console.warn(
+ `${providerConfiguration.configured.map((provider) => BROKERED_PROFILE[provider].envVar).join(" and ")} are both set and OPENBOT_BROKER_PROVIDER names neither, so no broker is active: connected apps can be neither connected nor revoked until it is set. Existing connections are read as ${BROKERED_PROFILE[brokerProvider].vendor}'s.`,
+ );
+}
+const sharedUseStore = createSharedUseStore(database, brokerProvider);
const pluginStore = createPluginStore({
database,
@@ -867,6 +935,7 @@ const pluginStore = createPluginStore({
* attempted.
*/
broker: composio?.broker,
+ brokerProvider,
});
/*
@@ -884,9 +953,13 @@ const accountModes = createAccountModeSwitch({
// Logo metadata is optional; a vendor outage must not prevent the API from starting.
if (composio) {
- void backfillComposioLogos(database, composio.broker).catch(() => {
+ void backfillBrokeredLogos(
+ database,
+ composio.provider,
+ composio.broker,
+ ).catch(() => {
console.warn(
- "Composio app logos could not be updated. Existing icons remain available; missing logos will be retried on the next restart.",
+ `${BROKERED_PROFILE[composio.provider].vendor} app logos could not be updated. Existing icons remain available; missing logos will be retried on the next restart.`,
);
});
}
@@ -2397,7 +2470,7 @@ const app = createApp(
// The app directory, behind the same client the plugin store and the transport already share.
// Absent without a key, which leaves the routes reporting no broker rather than listing apps
// nobody could connect.
- composio ? { broker: composio.broker } : undefined,
+ composio ?? undefined,
process.env.OPENBOT_MODEL_OAUTH_FILE?.trim()
? createProviderOAuthProxy(process.env.OPENBOT_MODEL_OAUTH_FILE.trim())
: undefined,
diff --git a/server/src/intelligence-entitlement-check.ts b/server/src/intelligence-entitlement-check.ts
index 265ed1a8c..cef575869 100644
--- a/server/src/intelligence-entitlement-check.ts
+++ b/server/src/intelligence-entitlement-check.ts
@@ -20,18 +20,29 @@ export type EntitlementWarning = {
fix: string;
};
-const FIXES: Record = {
- RUNTIME_ENTITLEMENTS_SELF_HOSTED_MISCONFIGURED:
+/**
+ * The codes this build has advice for. A Map, because the code is Intelligence's string rather than
+ * this deployment's: a plain object answers `toString` with an inherited function, which is truthy
+ * and so takes the place of the sentence the `||` below exists to supply.
+ */
+const FIXES: ReadonlyMap = new Map([
+ [
+ "RUNTIME_ENTITLEMENTS_SELF_HOSTED_MISCONFIGURED",
"Intelligence has no valid licence. Give Intelligence's own deployment a COPILOTKIT_LICENSE_TOKEN " +
- "(its Helm chart takes it as appApi.licenseSecret, or externalSecrets.secrets.license); setting it " +
- "on OpenBot alone is not enough. Until then memory and other licensed features are refused.",
- RUNTIME_ENTITLEMENTS_SELF_HOSTED_EXPIRED:
+ "(its Helm chart takes it as appApi.licenseSecret, or externalSecrets.secrets.license); setting it " +
+ "on OpenBot alone is not enough. Until then memory and other licensed features are refused.",
+ ],
+ [
+ "RUNTIME_ENTITLEMENTS_SELF_HOSTED_EXPIRED",
"Intelligence's licence has expired. Renew it and replace COPILOTKIT_LICENSE_TOKEN on Intelligence's " +
- "deployment. Until then memory and other licensed features are refused.",
- RUNTIME_ENTITLEMENTS_MANAGED_UNAVAILABLE:
+ "deployment. Until then memory and other licensed features are refused.",
+ ],
+ [
+ "RUNTIME_ENTITLEMENTS_MANAGED_UNAVAILABLE",
"Managed Intelligence could not resolve this project's entitlement just now. This is usually " +
- "temporary; licensed features may be refused until it recovers.",
-};
+ "temporary; licensed features may be refused until it recovers.",
+ ],
+]);
/** The warning a response deserves, or null when Intelligence answers ready and active. */
export function entitlementWarning(
@@ -52,7 +63,7 @@ export function entitlementWarning(
...(code ? { code } : {}),
...(response.error?.message ? { message: response.error.message } : {}),
fix:
- (code && FIXES[code]) ||
+ (code && FIXES.get(code)) ||
"Intelligence did not confirm this deployment's entitlement, so licensed features such as memory may be refused.",
};
}
diff --git a/server/src/plugins/access.ts b/server/src/plugins/access.ts
index 56a582f86..bb911cfa9 100644
--- a/server/src/plugins/access.ts
+++ b/server/src/plugins/access.ts
@@ -1,6 +1,7 @@
+import { toolkitOf as arcadeToolkitOf } from "./arcade";
import { schemeKind } from "./broker";
import type { CatalogueEntry } from "./catalogue";
-import { toolkitOf } from "./composio";
+import { toolkitOf as composioToolkitOf } from "./composio";
import type { TransportKind } from "./transport";
/**
@@ -23,6 +24,14 @@ import type { TransportKind } from "./transport";
* without filling in this function, and the test beside it enumerates every row shape that exists —
* which is the exhaustiveness the previous arrangement could not offer, since nothing connects three
* independent string comparisons.
+ *
+ * AND THIS IS THE ONE MODULE ALLOWED TO SPELL A BROKER'S NAME, which `brokered-provenance.test.ts`
+ * enforces by exempting this file and no other. A second broker turned `row.provenance === "composio"`
+ * from a reading of one column into a claim that there is only one broker — a claim each copy of the
+ * comparison made separately, in a module that had no reason to be edited when the second one
+ * arrived. So the name stops being a comparison anybody can write and becomes {@link BrokerProvider}
+ * and {@link brokeredProvenance}, asked here; everywhere else reads the resolved fields below. The
+ * exemption is not a privilege, it is the cost of having somewhere for the vocabulary to live.
*/
/** Whose credential a call goes out on. */
@@ -195,7 +204,118 @@ export class CatalogueTransportUnroutableError extends ServerUnresolvableError {
* compile-time door and the runtime one shut, rather than trading the second for the first.
*/
function isBrokerTransport(kind: TransportKind): boolean {
- return kind === "composio";
+ return brokeredProvenance(kind);
+}
+
+/**
+ * Which broker a row is reached through, for a row that is reached through one at all.
+ *
+ * A NAMED UNION RATHER THAN THE STRING THE COLUMN HOLDS, because the column holds every provenance
+ * there is — `first-party`, `custom`, a broker's name — and only these two mean "an app somebody
+ * enabled at a broker, reached with the deployment's key and kept apart by an id we send". Code that
+ * compared the column to a literal could not say which of those it meant, so the comparison had to be
+ * re-derived wherever it was needed, and re-derived wrongly once a second broker existed.
+ *
+ * THE VALUES ARE THE COLUMN'S OWN SPELLINGS, not a mapping away from them. `brokered_connections.provider`
+ * is constrained to exactly `composio` and `arcade`, and `mcp_servers.provenance` writes the same two
+ * words, so a translation table here would be a second vocabulary to keep in step with the database's
+ * for no gain. What this type adds is that the compiler now knows the set is closed: a third broker is
+ * a change to this line, and every `Record` below stops compiling until it is
+ * answered for.
+ */
+export type BrokerProvider = Extract;
+
+/**
+ * Whether a provenance names a broker — the one place that question is asked.
+ *
+ * CRITERION. No module outside this one compares a provenance or a provider column to a vendor's
+ * name. `brokered-provenance.test.ts` is what says so, and this predicate is what that test leaves
+ * callers with instead.
+ *
+ * REASON. The comparison it replaces was correct and complete for as long as there was one broker,
+ * and every copy of it became a silent `false` for Arcade rows on the day there were two — not a
+ * failure, a row quietly resolving as somebody's MCP endpoint and being dialled as one. A function
+ * cannot do that: adding a broker to the union above changes every caller's answer at once, and the
+ * ones that must NOT change are then the ones that have to say so explicitly.
+ *
+ * TAKES A `string`, NOT A `BrokerProvider`, because every caller is holding a database column. A
+ * parameter narrowed to the union would push the unsafe cast back out to each call site, which is
+ * precisely the thing being collected here. The type predicate is the point: `row.provenance` goes
+ * in as a string and comes out narrowed, so the tables keyed on the union below are reachable
+ * without an assertion.
+ */
+export function brokeredProvenance(p: string): p is BrokerProvider {
+ return p === "composio" || p === "arcade";
+}
+
+/**
+ * Which app at which broker, read from the url by the parser belonging to that broker.
+ *
+ * ONE PARSER PER PROVIDER, AND THEY ARE NOT INTERCHANGEABLE. `composio://gmail` and `arcade://Gmail`
+ * are different schemes naming apps under different rules: Composio's slugs are lower case by
+ * convention and Arcade's toolkit names are case-sensitive and dotted, so each module's `toolkitOf`
+ * answers null for the other's url by design. Dispatching on the provider rather than trying both
+ * parsers is what keeps that null meaning "this row's url does not name an app at its own broker" —
+ * the thing the brokered gate needs — instead of "no broker recognised it", which would let a row
+ * provenanced at one broker resolve against the other's parse of the same string.
+ *
+ * A `Record` keyed on {@link BrokerProvider} rather than a branch, so a broker added to the union
+ * cannot be resolved until the url it is dialled at can be read.
+ */
+const TOOLKIT_OF: Record string | null> = {
+ composio: composioToolkitOf,
+ arcade: arcadeToolkitOf,
+};
+
+/**
+ * What each broker's rows are minted as: the url scheme, the id namespace and the vendor label.
+ *
+ * ONE ROW PER PROVIDER, KEYED ON {@link BrokerProvider}, so a third broker cannot be enabled until
+ * it says how its rows are spelled. The provenance column is the provider's own name and needs no
+ * entry here. The id prefix is what prefixes every tool name and what a grant and a policy rule are
+ * written against, which is why `store.ts` reserves EVERY provider's and not only the active one's:
+ * a deployment that switches broker must not find a typed server already sitting in the namespace
+ * the other path mints into. The scheme is reserved the same way one step in — the screen name
+ * `/admin/plugins/` is a static route on each broker — and that reservation is derived
+ * from these keys rather than spelled again, because a reservation spelled twice is the one that
+ * gets added to once.
+ *
+ * AND IT LIVES HERE RATHER THAN IN `store.ts`, BESIDE {@link TOOLKIT_OF}, for that table's reason.
+ * This module is where a broker's vocabulary is allowed to be spelled, and it is the one file
+ * `brokered-provenance.test.ts` exempts. A scheme literal in `store.ts` is a second home for the
+ * same word, and the guard that keeps the first home the only one cannot be absolute while the
+ * record it reads from sits outside it. `shared-use-store.ts` had to derive its own `${provider}://`
+ * for exactly as long as this was private here, which is what two homes costs: one table, one
+ * spelling, read by everybody.
+ */
+export const BROKERED_PROFILE: Record<
+ BrokerProvider,
+ { scheme: string; idPrefix: string; vendor: string; envVar: string }
+> = {
+ composio: {
+ scheme: "composio://",
+ idPrefix: "composio-",
+ vendor: "Composio",
+ envVar: "COMPOSIO_API_KEY",
+ },
+ arcade: {
+ scheme: "arcade://",
+ idPrefix: "arcade-",
+ vendor: "Arcade",
+ envVar: "ARCADE_API_KEY",
+ },
+};
+
+/**
+ * {@link TOOLKIT_OF}, reachable. A function so a caller cannot index the table with a widened
+ * string, and so the table stays private to the one module that argues for it: a second dispatch
+ * anywhere else is a second place for a provider to be missed.
+ */
+export function toolkitOfFor(
+ provider: BrokerProvider,
+ url: string,
+): string | null {
+ return TOOLKIT_OF[provider](url);
}
/**
@@ -207,7 +327,7 @@ function isBrokerTransport(kind: TransportKind): boolean {
* from an old backup, written by a bug — cannot turn a reviewed vendor into a brokered one and start
* sending its calls somewhere else. The row only ever answers where the catalogue is silent.
*
- * IT CUTS BOTH WAYS, WHICH IS WHY `composio` IS REFUSED RATHER THAN OVERRULED. Only one direction
+ * IT CUTS BOTH WAYS, WHICH IS WHY A BROKERED PROVENANCE IS REFUSED RATHER THAN OVERRULED. Only one direction
* was considered when that order was written: a brokered row whose id collides with a curated slug
* was quietly answered as the curated vendor. Nothing in these two arguments tells that row apart
* from a tampered curated one, so the only answer that is not wrong in one of the two worlds is no
@@ -234,16 +354,16 @@ export function accessFor(
},
entry: CatalogueEntry | null,
): ServerAccess {
- if (entry && row.provenance === "composio") {
+ if (entry && brokeredProvenance(row.provenance)) {
throw new ServerRowAmbiguousError(
- `${entry.key} is a server this deployment ships an entry for, and a row with that id says its provenance is composio. Nothing can tell an edited column from a brokered app that took the name, so this row is not resolved at all: rename it, or correct its provenance.`,
+ `${entry.key} is a server this deployment ships an entry for, and a row with that id says its provenance is ${row.provenance}. Nothing can tell an edited column from a brokered app that took the name, so this row is not resolved at all: rename it, or correct its provenance.`,
);
}
if (entry) {
if (isBrokerTransport(entry.transport ?? "mcp")) {
throw new CatalogueTransportUnroutableError(
- `${entry.key} is a catalogue entry declaring the composio transport, which is reached from a row's provenance and the app slug in its url — neither of which an entry has. There is no app to broker to and no connection to check, so this entry is not resolved at all: give it the transport it is actually reached over.`,
+ `${entry.key} is a catalogue entry declaring the ${entry.transport ?? "mcp"} transport, which is a broker's and is reached from a row's provenance and the app slug in its url — neither of which an entry has. There is no app to broker to and no connection to check, so this entry is not resolved at all: give it the transport it is actually reached over.`,
);
}
@@ -255,7 +375,7 @@ export function accessFor(
};
}
- if (row.provenance === "composio") {
+ if (brokeredProvenance(row.provenance)) {
/*
* AND A BROKERED APP THAT NEEDS NO CREDENTIAL IS REACHED AS THE DEPLOYMENT, NOT AS THE ASKER.
*
@@ -281,15 +401,22 @@ export function accessFor(
* asked, the call lands in one account that is nobody's in particular, and naming the asker as
* the account would assert an attribution that does not exist. The asker is still on the row as
* `actor`.
+ *
+ * NEITHER OF THOSE TWO READINGS IS THE BROKER'S, which is why adding a second broker leaves this
+ * branch alone. `schemeKind` and `accountMode` are columns on OUR row describing what the app was
+ * created as and whether it was switched to Shared, and both answer the same question about an
+ * Arcade app that they answer about a Composio one. Only WHICH broker and WHICH app are the
+ * provider's business, and those are the two fields below.
*/
+ const provider = row.provenance;
return {
- transport: "composio",
+ transport: provider,
credential: "brokered",
reachedAs:
schemeKind(row.authScheme) === "none" || row.accountMode === "shared"
? "deployment"
: "person",
- toolkit: toolkitOf(row.url),
+ toolkit: TOOLKIT_OF[provider](row.url),
};
}
diff --git a/server/src/plugins/arcade-adapter.ts b/server/src/plugins/arcade-adapter.ts
new file mode 100644
index 000000000..be67c89fd
--- /dev/null
+++ b/server/src/plugins/arcade-adapter.ts
@@ -0,0 +1,1801 @@
+import Arcade, { APIError } from "@arcadeai/arcadejs";
+import {
+ type ArcadeActions,
+ type ArcadeFailure,
+ ArcadeFailureError,
+ authorizationRequirementOf,
+ classify,
+ requiredScopesOf,
+ shapeOf,
+ vendorStatusOf,
+} from "./arcade";
+import {
+ type BrokerApp,
+ type BrokerConnection,
+ BrokerRefusalError,
+ type ConnectedAppBroker,
+ flagOf,
+ type GrantState,
+} from "./broker";
+
+/**
+ * THE VENDOR'S ONLY DOOR INTO THE SERVER. This is the one file under `server/src` that imports
+ * `@arcadeai/arcadejs`, so a version bump has exactly this file to be read against, the same
+ * convention `createComposioClient` states for its SDK. It imports the transport (`./arcade`) for
+ * the `ArcadeFailure` shape; the transport never imports it, because that direction is a cycle and
+ * `broker.ts` records why.
+ */
+
+/** Deeper than any real wrapping; a bound, so a cyclic `cause` cannot loop. */
+const MAX_CAUSE_DEPTH = 16;
+
+/**
+ * THE TEXT A VENDOR FIELD CARRIES, OR NOTHING — AND A STRING WITH NO TEXT IN IT IS NOTHING.
+ *
+ * `""` is absence wearing a value's clothes, and answering it as a value is what breaks every
+ * caller that then writes `textOf(a) ?? textOf(b)`: `??` falls through on null and undefined ONLY,
+ * so an empty `provider_user_info.name` SHADOWED a perfectly good `login` and the Connected-accounts
+ * screen drew a blank name on a connection that is genuinely connected. The emptiness belongs here,
+ * in the function that claims to answer "is this text", and not in a `||` at each call site — one
+ * of which would be forgotten. Whitespace-only is the same fact: a name of three spaces draws the
+ * same blank.
+ *
+ * The value is returned UNTOUCHED when it has text; this answers whether there is any, it does not
+ * reformat what the vendor sent. `authorize` below already spells this rule inline for `url` and
+ * the authorization id (`typeof x !== "string" || x === ""`), which is the same judgement made one
+ * field at a time.
+ */
+const textOf = (value: unknown): string | undefined =>
+ typeof value === "string" && value.trim() !== "" ? value : undefined;
+
+/**
+ * Walk the `cause` chain to the FIRST `APIError` and read its `status` and the response body's
+ * `error.name` / `error.message`. The SDK's own fields are `status`, `headers` and `error` (the
+ * parsed body); nothing on it names the tool, so nothing here looks for one. No `APIError` anywhere
+ * in the chain answers `null`.
+ *
+ * `status` is undefined for `APIConnectionError` and `APIUserAbortError`, which extend `APIError`:
+ * no response was ever read, so the outcome is uncertain, not refused.
+ */
+export function normalizeArcadeError(error: unknown): ArcadeFailure | null {
+ const seen = new Set();
+ let current: unknown = error;
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth += 1) {
+ if (current === null || typeof current !== "object" || seen.has(current))
+ return null;
+ seen.add(current);
+ /*
+ * A REFUSAL THIS FILE ALREADY NORMALIZED ANSWERS ITSELF, and it is read on the same chain walk
+ * rather than only at the top, because {@link vendorCall}'s answer is carried as a `cause` by
+ * the one place that wraps it again (`revoke`, which fails over several connections at once).
+ *
+ * THIS IS WHAT KEEPS THE MEDIATION FROM COSTING ANYTHING. Every reader of a failure out of this
+ * adapter — the authorize route's definite-or-uncertain decision, keyed on `status`; `callTool`'s
+ * authorize prompt and `confirmReturn`'s `user_mismatch`, both keyed on the body's `error.name`
+ * — asks through this function, so stopping the vendor's own `APIError` at the adapter boundary
+ * takes nothing away from any of them. The vendor's PROSE is the only thing that does not come
+ * back out; see {@link ArcadeFailureError}, which drops it.
+ */
+ if (current instanceof ArcadeFailureError) return current.failure;
+ if (current instanceof APIError) {
+ const body = current.error;
+ const record =
+ body !== null && typeof body === "object"
+ ? (body as Record)
+ : {};
+ return {
+ status: typeof current.status === "number" ? current.status : undefined,
+ name: textOf(record.name),
+ message: textOf(record.message),
+ };
+ }
+ current = (current as { cause?: unknown }).cause;
+ }
+ return null;
+}
+
+/**
+ * THIS DEPLOYMENT'S ONE SENTENCE FOR "ARCADE ANSWERED, AND THE ANSWER HAD NO BODY IN IT".
+ *
+ * ONE SENTENCE AND NOT SEVERAL, because the fact is one fact wherever it lands: the socket carried
+ * a status and no readable value under it — an empty 204, or a body whose whole JSON is `null`.
+ * It is written in the uncertainty register {@link vendorCall}'s `APIConnectionError` sentence uses
+ * ("is not known here") rather than the refusal register ("Arcade refused …"), because an empty
+ * answer is not the vendor declining: whether the call took effect is genuinely unknown, and every
+ * caller that spends a DELETE off this reading must treat it as unknown.
+ *
+ * NOT A SHAPE QUOTED BACK. The body is the thing this file exists to keep off an admin page; see
+ * {@link vendorCall}. "No body" says all there is to say without quoting one.
+ */
+const unreadableBody = (step: string): string =>
+ `Arcade answered ${step} with no body this deployment could read — an empty response, or one whose JSON is the literal null — so what it says is not known here.`;
+
+/**
+ * THE ANSWER'S BODY, OR THIS DEPLOYMENT'S REFUSAL — never a dereference of `null`.
+ *
+ * FOR THE CALLS THAT ARE NOT PAGED LISTINGS. A page goes through `OffsetPage`'s constructor, which
+ * raises on a null body and is caught in {@link vendorCall}; `tools.authorize`, `auth.status` and
+ * `auth.confirmUser` go through no constructor at all, so the SDK hands a literal `null` straight
+ * back WEARING ITS DECLARED RESPONSE TYPE. The generated types are a description of what Arcade
+ * sends today and are validated by nothing — the same reading `confirmReturn`'s `auth_id` check
+ * already states one field down — so reading `answer.status` off one of those is the identical
+ * unguarded dereference, raising the identical bare `TypeError` from inside this file instead.
+ *
+ * THE FIELD-LEVEL GUARDS BELOW STAY EXACTLY AS THEY ARE: this establishes only that there is an
+ * object to read fields off, and says nothing about what is in it.
+ */
+function requireBody(step: string, answer: T): T & object {
+ if (answer === null || typeof answer !== "object") {
+ throw new Error(unreadableBody(step));
+ }
+ return answer as T & object;
+}
+
+/**
+ * ONE CALL TO THE VENDOR, WITH ITS REFUSAL TURNED INTO THIS DEPLOYMENT'S OWN SENTENCE.
+ *
+ * EVERY `client.*` CALL IN THIS FILE GOES THROUGH HERE, and that uniformity is the point rather
+ * than a tidiness: the thing being confined is the SDK's `APIError`, whose `message` is
+ * `${status} ${the response body}` (`core/error.js: makeMessage`), and one unwrapped call is one
+ * path on which a vendor body reaches a log line, the Plugins page's `lastError`, a model's context
+ * or an audit row. On this vendor a body is not inert — the auth paths answer with a bearer `url`
+ * and a live `context.token` — and the claim this adapter is held to is that neither ever reaches
+ * any of those. `step` is written HERE, at the call site, for the reason `callTool`'s refusal names
+ * the action from the call site: nothing in an `APIError` says what was being asked.
+ *
+ * WHAT IS NOT AN SDK ERROR IS RETHROWN UNTOUCHED, which is deliberate: it leaves this file's own
+ * refusals — the malformed-listing sentences, the mint with no url — exactly as they were.
+ *
+ * WITH ONE EXCEPTION, AND IT IS THE BODY THE SDK COULD NOT READ. A 204 with `content-length: 0`,
+ * and a body whose JSON is the literal `null`, both reach `OffsetPage`'s constructor as `body ===
+ * null` and raise a bare `TypeError: null is not an object (evaluating 'body.items')` from inside
+ * `@arcadeai/arcadejs`, before one line of this file has looked at anything. Left to escape as
+ * itself, that `TypeError` is the WRONG ANSWER TWICE: an operator reading the Plugins page gets a
+ * vendor stack trace instead of this deployment's sentence, indistinguishable in the logs from a
+ * programmer error here; and the only reason the revoke invariant holds on those two shapes is
+ * that a crash is technically "not answering" — one tidy-up of the cast that raises it (a `?? []`,
+ * say) turns both into `revoke` answering `false` with no DELETE spent, which is the live defect
+ * `broker.ts:712-717` forbids. So the body this deployment could not read gets a REFUSAL, the same
+ * one {@link requireBody} writes for the non-paged calls, and never a fallback to an empty list.
+ *
+ * THE ORDER IS WHAT MAKES THAT SAFE, and it is the whole of the old warning against catching "by
+ * shape": `normalizeArcadeError` IS ASKED FIRST and every `APIError` answers it, so nothing
+ * reaching the `TypeError` branch carries a vendor response at all. The `run()` closures here are
+ * one `client.*` call each and nothing else, so a `TypeError` with no `APIError` anywhere in its
+ * chain is the SDK failing to read an answer, which is exactly what the sentence says.
+ *
+ * A REFUSAL THAT IS ALREADY MEDIATED PASSES STRAIGHT THROUGH, so a nested call cannot dress one
+ * sentence up in another.
+ */
+async function vendorCall(step: string, run: () => Promise): Promise {
+ try {
+ return await run();
+ } catch (error) {
+ if (error instanceof ArcadeFailureError) throw error;
+ const failure = normalizeArcadeError(error);
+ if (!failure) {
+ if (error instanceof TypeError) throw new Error(unreadableBody(step));
+ throw error;
+ }
+ throw new ArcadeFailureError(
+ failure.status === undefined
+ ? // `status` is undefined for `APIConnectionError` and `APIUserAbortError`: no response was
+ // ever read, so the outcome is uncertain rather than refused, and the sentence says so —
+ // the authorize route keys exactly that distinction off `status`.
+ `Arcade did not answer ${step}, so whether it ran is not known here.`
+ : `Arcade refused ${step} with HTTP ${failure.status}.`,
+ failure,
+ );
+ }
+}
+
+/**
+ * Page size for the PER-TOOLKIT action listings, `grantsFor` and `authProvidersNamedBy`. A short
+ * page ends it, never `total_count`. The whole-directory listing behind `listApps` does NOT use
+ * this: see {@link DIRECTORY_PAGE_SIZE}.
+ *
+ * THE MEASURED WORST CASE, so the next person does not re-scan the API: across Arcade's directory
+ * (8,320 tools, 129 toolkits) the largest toolkit is **`GithubApi`, 818 actions** — then
+ * `PosthogApi` 766, `DatadogApi` 588, `ZohoBooksApi` 511, `HubspotCrmApi` 453. At 100 a page
+ * costs ~1.19 s, so `GithubApi` read SEQUENTIALLY was 9 pages, ~10.7 s, past the 10 s
+ * `VENDOR_CALL_TIMEOUT_MS` that wraps the grants screen and Disconnect alike: neither could load.
+ * Under {@link CATALOGUE_CONCURRENCY} it is page one, then the other 8 in one wave, ~2.4 s.
+ *
+ * KEPT AT 100 RATHER THAN RAISED TO THE DIRECTORY'S 1000, deliberately. Latency, not size,
+ * dominates (~1.19 s at 100, ~1.8 s at 1000), so two waves of 100 cost about what one page of
+ * 1000 does. At 1000 every toolkit Arcade publishes would be ONE page, and the paths that make a
+ * per-toolkit read trustworthy — a short page ending it, `seen < total` judged across pages, a
+ * provider disagreeing with itself past page one — would stop being exercised by any real toolkit
+ * while still being the only defence when one grows. At 100, every toolkit over 100 actions
+ * crosses page boundaries on every read, so the completeness check stays a live check.
+ */
+const CATALOGUE_PAGE_SIZE = 100;
+
+/**
+ * How many per-toolkit action pages are in flight at once after page one has said how many there
+ * are, through the same opt-in {@link Listing.concurrency} the directory uses.
+ *
+ * EIGHT, because it covers `GithubApi` (818 actions → 8 pages after the first) in ONE wave while
+ * still bounding what a lying `total_count` can fire. The deadline arithmetic is
+ * `~1.19 s × (1 + ⌈remaining / 8⌉)`: under 10 s up to 56 remaining pages, a toolkit of ~5,700
+ * actions, for the grants screen. Disconnect spends ~3 more sequential calls after this listing
+ * (auth providers, connections, the DELETE; ~1.2 s each, not measured separately), which leaves
+ * room for 32 remaining pages, a toolkit of ~3,300 actions — four times `GithubApi`. Past that is
+ * the number to revisit.
+ */
+const CATALOGUE_CONCURRENCY = 8;
+/** A runaway guard, not a bound. */
+const CATALOGUE_MAX_PAGES = 1000;
+
+/**
+ * Page size for the WHOLE-DIRECTORY listing `listApps` reads, which is the "Browse Arcade" screen.
+ *
+ * THE ARITHMETIC, MEASURED AGAINST THE LIVE API so the next person changing this does not re-probe:
+ * - Arcade's directory reported `total_count` **8,320** tools.
+ * - `limit=1000` is accepted (1,000 items in ~1.77 s); `limit=5000` is REJECTED (empty answer in
+ * ~0.17 s), so **1000 is at or near the vendor's ceiling** — do not raise this past it.
+ * - A page costs **~1.8 s** at limit 1000 (~1.2 s at 100, ~1.8 s at 250): latency, not size,
+ * dominates, so fewer larger pages are nearly free.
+ *
+ * At the old page size of 100 that was 84 SEQUENTIAL pages, ~100 s, against a 10 s
+ * `VENDOR_CALL_TIMEOUT_MS` that wraps the WHOLE `listApps` — so the directory could never load. At
+ * 1000 it is 9 pages: one, then the remaining 8 at once under {@link DIRECTORY_CONCURRENCY}, ~3.6 s.
+ */
+const DIRECTORY_PAGE_SIZE = 1000;
+
+/**
+ * How many directory pages are in flight at once after page one has said how many there are.
+ *
+ * EIGHT, because it covers today's directory in ONE wave (8,320 tools → 8 pages after the first)
+ * while still being a bound: a `total_count` of a million fires eight requests at a time, never a
+ * thousand. The deadline arithmetic is `~1.8 s × (1 + ⌈remaining / 8⌉)`, which stays under the 10 s
+ * `VENDOR_CALL_TIMEOUT_MS` up to 32 remaining pages — a directory of roughly 33,000 tools, four times
+ * today's. Past that the listing will time out again, and that is the number to revisit.
+ */
+const DIRECTORY_CONCURRENCY = 8;
+
+/**
+ * WHAT ARCADE ITSELF SENT: the parsed response body, and never the SDK's page object.
+ *
+ * `OffsetPage`'s constructor MANUFACTURES DEFAULTS for the two fields every listing here reads —
+ * `this.items = body.items || []` and `this.total_count = body.total_count || 0` — so a field the
+ * vendor never sent presents on the page as a well-formed empty answer. Read off the page, "Arcade
+ * sent no items" is INDISTINGUISHABLE FROM "Arcade sent an empty list" and "Arcade sent no total"
+ * is INDISTINGUISHABLE FROM "Arcade said zero": the `Array.isArray` guards below can never fire,
+ * every `typeof reported === "number"` test passes on an absent field, and a malformed or truncated
+ * listing commits as a complete one.
+ *
+ * `AbstractPage` keeps the parsed body untouched beside it, so the honest values are read from
+ * there. The answer's own properties are the fallback for an answer that has NO `body` at all,
+ * which is what `admin.authProviders.list` (a plain `APIPromise`, not a page) and every hand-built
+ * stub return — those were never passed through a page constructor, so nothing was manufactured.
+ *
+ * AND THE PRESENCE OF `body` IS WHAT DECIDES WHICH OF THE TWO THIS IS — never whether the body
+ * happens to be readable. `AbstractPage`'s constructor always assigns `body`, so the property
+ * existing is the answer saying "I went through a page constructor, and my own `items` and
+ * `total_count` are that constructor's inventions". From there the body is the ONLY honest
+ * reading, and a body that is not a record means THE ANSWER COULD NOT BE READ — it does not mean
+ * the answer was empty.
+ *
+ * MEASURED, because this is the hole the fallback left and it is the live defect again: a 200
+ * carrying `content-type: text/html` — a proxy's courtesy page — parses to a STRING
+ * (`internal/parse.js` returns `response.text()` for anything that is not JSON), so no record
+ * exists anywhere and a `?? page` fallback hands back the page's manufactured `items: []`.
+ * `listApps` then answered `[]`, `isConnected` answered false, and `revoke` answered `false` WITH
+ * NO DELETE SPENT while the caller deleted the local row — `broker.ts:712-717`'s forbidden
+ * outcome, reached by a route neither contributing guard could see, because both were written
+ * against MALFORMED JSON and this is the branch where the JSON was never parsed at all.
+ *
+ * ANSWERING `null` IS THE REFUSAL, and deliberately not a throw from here: every caller already
+ * has this deployment's own sentence for an unreadable listing — {@link pagedItems}' per-listing
+ * `malformed`, and the transport's own `Array.isArray` guard for `listTools` — and routing through
+ * them is what keeps one vocabulary on this seam instead of a second error shape invented here.
+ */
+function sentBody(answer: unknown): Record | null {
+ const page = record(answer);
+ if (!page) return null;
+ if ("body" in page) return record(page.body);
+ return page;
+}
+
+/** A count's decimal spelling and nothing else: no sign, no exponent, no separator, no point. */
+const COUNT_TEXT = /^[0-9]+$/;
+
+/**
+ * A COUNT ARCADE SENT, READ AS THREE OUTCOMES AND NEVER TWO: the number, `undefined` for a field
+ * the vendor did not send, and `"unreadable"` for one it DID send in a shape this cannot read.
+ *
+ * THE THIRD OUTCOME IS THE WHOLE POINT. The previous reading was `typeof raw === "number" ? raw :
+ * undefined`, which folded "the vendor did not say" together with "the vendor said something this
+ * deployment cannot read" — and every caller treats the first as "there is nothing to cross-check
+ * against". Measured: a vendor that serves 100 of 250 connections and spells the claim
+ * `"total_count": "250"` had its truncation cross-check silently switched off, and `revoke`
+ * DELETED 100, ANSWERED `true`, AND LEFT 150 GRANTS LIVE with no signal anywhere that the
+ * withdrawal was partial. That is strictly worse than the `false`-with-no-DELETE defect this file
+ * is full of warnings about: that one is at least visibly wrong.
+ *
+ * A NUMERIC STRING IS A LEGITIMATE SPELLING AND IS COERCED. `"250"` is the vendor saying 250 — it
+ * is unambiguous, it is what a serializer that renders integers as text sends, and refusing it
+ * would take Disconnect permanently offline for a deployment whose vendor always spells it so,
+ * trading a silent partial for a hard failure with no way through. Coercing instead makes the
+ * cross-check FIRE, which is what actually protects the invariant: the 250-of-100 listing above
+ * now throws the `incomplete` sentence, no DELETE is spent, and the local row survives.
+ *
+ * EVERYTHING ELSE PRESENT REFUSES, and the strictness is deliberate, because a lenient parse is
+ * how this defect comes back. `"many"`, `null`, an object, `true`, `NaN`, `Infinity`, a negative,
+ * a fraction and `"1e3"` are all unreadable: anything that is not a plain non-negative decimal
+ * integer is a value whose meaning this deployment would be guessing at. `NaN` in particular was
+ * already slipping through the old `typeof` test and disabling the cross-check the same way a
+ * string did — `seen < NaN` is false — so the number branch checks the VALUE and not just the type.
+ *
+ * ABSENT STAYS ABSENT. `total_count` is optional on the wire and the listings here end on a short
+ * page, never on a total; a missing field must go on meaning "the vendor did not say" or every
+ * engine that omits it would refuse.
+ */
+function countOf(raw: unknown): number | "unreadable" | undefined {
+ if (raw === undefined) return undefined;
+ if (typeof raw === "string") {
+ const text = raw.trim();
+ if (!COUNT_TEXT.test(text)) return "unreadable";
+ const parsed = Number(text);
+ return Number.isSafeInteger(parsed) ? parsed : "unreadable";
+ }
+ if (typeof raw !== "number") return "unreadable";
+ return Number.isSafeInteger(raw) && raw >= 0 ? raw : "unreadable";
+}
+
+/** What the vendor sent, written into a refusal without pasting an object into a sentence. */
+function shownValue(value: unknown): string {
+ if (typeof value === "string") return JSON.stringify(value);
+ if (value !== null && typeof value === "object") return "an object";
+ return String(value);
+}
+
+/**
+ * The `total_count` Arcade itself sent, or nothing. Read through {@link sentBody}, for its reason,
+ * and through {@link countOf}, for ITS reason: a total the vendor sent in a shape this cannot read
+ * THROWS the caller's own sentence rather than answering `undefined` and disabling the only
+ * cross-check that can tell a truncated listing from a whole one.
+ */
+function reportedTotalOf(
+ answer: unknown,
+ unreadable: (shown: string) => string,
+): number | undefined {
+ const raw = sentBody(answer)?.total_count;
+ const count = countOf(raw);
+ if (count === "unreadable") throw new Error(unreadable(shownValue(raw)));
+ return count;
+}
+
+/**
+ * The `items` Arcade itself sent, UNCHECKED — the caller's own guard is what decides whether this
+ * is a list. Read through {@link sentBody}, for its reason: off the page object, a listing that
+ * sent no `items` at all reads as an empty one and no guard anywhere can tell them apart.
+ */
+function sentItemsOf(answer: unknown): unknown {
+ return sentBody(answer)?.items;
+}
+
+/**
+ * The HIGHEST total any page reported, never the LAST one.
+ *
+ * Keeping the last page's value loses the same way `reportedTotalOf` describes, one step later: an
+ * engine that truncates at 250 of 300 and drops `total_count` on its short page would otherwise
+ * erase the 300 that page one reported, and the cross-check would have nothing to compare against.
+ */
+const highestTotal = (
+ total: number | undefined,
+ reported: number | undefined,
+): number | undefined =>
+ reported === undefined
+ ? total
+ : total === undefined
+ ? reported
+ : Math.max(total, reported);
+
+/** One listing's page size, runaway guard, page call and the three sentences a failure carries. */
+type Listing = {
+ pageSize: number;
+ maxPages: number;
+ page: (offset: number) => Promise;
+ malformed: string;
+ unending: string;
+ incomplete: (seen: number, total: number) => string;
+ /**
+ * What this deployment says when the vendor sent a `total_count` it cannot read. A sentence of
+ * its own rather than a reuse of `malformed`: the list itself was fine and the CLAIM ABOUT ITS
+ * LENGTH was not, and a reader sent looking for a broken list would find a well-formed one.
+ */
+ unreadableTotal: (shown: string) => string;
+ /**
+ * How many pages may be in flight at once AFTER PAGE ONE. Absent or 1 is the strictly sequential
+ * read the connection and auth-provider listings use. `listApps` and the per-toolkit action
+ * listings (`grantsFor`, `authProvidersNamedBy`) turn it on. Above 1, page one's `total_count` decides which further
+ * offsets are fetched ahead, at most this many at a time; see {@link pagedItems}.
+ */
+ concurrency?: number;
+};
+
+/**
+ * `run`, at most `limit` at a time, and NOT AT ALL once `abandoned()` says the reader has stopped.
+ *
+ * The second half is what keeps a lying `total_count` from costing anything: a listing that claims
+ * a million rows and then ends on page two must not go on firing the 998 requests queued behind it.
+ */
+function bounded(
+ limit: number,
+ abandoned: () => boolean,
+): (run: () => Promise) => Promise {
+ let active = 0;
+ const waiting: (() => void)[] = [];
+ return (run: () => Promise) =>
+ new Promise((resolve, reject) => {
+ const start = () => {
+ if (abandoned()) {
+ resolve(undefined);
+ waiting.shift()?.();
+ return;
+ }
+ active += 1;
+ // Through an executor, so a call that throws synchronously is a rejection like any other.
+ new Promise((answered) => answered(run()))
+ .then(resolve, reject)
+ .finally(() => {
+ active -= 1;
+ waiting.shift()?.();
+ });
+ };
+ if (active < limit) start();
+ else waiting.push(start);
+ });
+}
+
+/**
+ * ONE LISTING READ TO ITS END, as a stream of items.
+ *
+ * PAGINATION ENDS ON A SHORT PAGE, never on `total_count`, which is optional on the wire; the
+ * reported total is a CROSS-CHECK and is taken through {@link reportedTotalOf} and
+ * {@link highestTotal} so that "the vendor did not say" can never read as "the vendor said zero"
+ * — nor, through {@link countOf}, as "the vendor said something unreadable". A cross-check with no
+ * input is not a cross-check, and the listing it would have caught is the truncated one.
+ *
+ * THE SDK'S OWN AUTO-PAGINATION IS NOT USED by anything that goes through here, and that is the
+ * point: `OffsetPage.nextPageRequestOptions` returns `null` the moment `total_count` is falsy, so
+ * `for await (… of client.x.list(…))` silently stops after page one against any engine that omits
+ * the field — and the caller cannot tell a complete answer from a first page of it.
+ *
+ * THE INCOMPLETENESS THROW IS THE LAST THING THIS YIELDS, after every item, so a caller that folds
+ * items as they arrive still never sees a short listing finish as a whole one.
+ *
+ * FETCHING AHEAD ({@link Listing.concurrency}) CHANGES WHEN A PAGE IS ASKED FOR, NEVER HOW IT IS
+ * READ. Page one is asked for alone; its `total_count` names the offsets that should exist, and
+ * those are requested at once under the bound. The answers are then consumed IN OFFSET ORDER by
+ * exactly the loop below, so every rule above still holds page by page: a short page still ends the
+ * listing (the pages fetched past it are discarded unread and the queued ones never fire), a total
+ * that UNDER-reports still reads on sequentially from where the prefetch ran out, and `seen < total`
+ * still throws. A page ahead that FAILED fails the listing when the loop reaches it, and not before.
+ * Without a readable total on page one there is nothing to fetch ahead of, and it reads sequentially.
+ */
+async function* pagedItems(listing: Listing): AsyncGenerator {
+ let seen = 0;
+ let total: number | undefined;
+ let ended = false;
+ let stopped = false;
+ const ahead = new Map>();
+ try {
+ for (let index = 0; index < listing.maxPages; index += 1) {
+ const fetched = ahead.get(seen);
+ ahead.delete(seen);
+ const answer = await (fetched ?? listing.page(seen));
+ const items = sentItemsOf(answer);
+ if (!Array.isArray(items)) throw new Error(listing.malformed);
+ yield* items;
+ seen += items.length;
+ total = highestTotal(
+ total,
+ reportedTotalOf(answer, listing.unreadableTotal),
+ );
+ if (items.length < listing.pageSize) {
+ ended = true;
+ break;
+ }
+ if (
+ index === 0 &&
+ total !== undefined &&
+ (listing.concurrency ?? 1) > 1
+ ) {
+ const slot = bounded(listing.concurrency ?? 1, () => stopped);
+ // `floor`, not `ceil - 1`: a total that is an exact multiple of the page size still needs
+ // the EMPTY page after it, because a short page — never the total — is what ends a listing.
+ const last = Math.min(
+ Math.floor(total / listing.pageSize),
+ listing.maxPages - 1,
+ );
+ for (let page = 1; page <= last; page += 1) {
+ const offset = page * listing.pageSize;
+ const pending = slot(() => listing.page(offset));
+ // Observed here so a page fetched past the end — or past a failure the loop throws on
+ // first — never surfaces as an unhandled rejection. The loop still awaits the original.
+ pending.catch(() => undefined);
+ ahead.set(offset, pending);
+ }
+ }
+ }
+ } finally {
+ stopped = true;
+ }
+ if (!ended) throw new Error(listing.unending);
+ if (total !== undefined && seen < total) {
+ throw new Error(listing.incomplete(seen, total));
+ }
+}
+
+/** The verdict `classify` falls back to when it could not read a behaviour. See {@link isClassified}. */
+const CLOSED_VERDICT = { effect: "write", destructive: true } as const;
+
+/**
+ * Whether ARCADE classified this action, as against this deployment falling back for it.
+ *
+ * `classify` from `./arcade` is the one reading of `metadata.behavior` anywhere here, and it is
+ * deliberately TOTAL: anything it cannot read answers the closed `{ write, destructive }` verdict,
+ * so an unannotated action is approved like a deletion. That fallback is exactly what must not be
+ * counted — counted, `AirtableApi`'s `metadata: null` actions would report as classified, and the
+ * coverage warning that exists to name them would never fire.
+ *
+ * AN EMPTY `behavior` IS NOT A CLASSIFICATION EITHER, and `classify` is where that is decided: a
+ * block stating neither flag answers the closed verdict, so it arrives here as the fallback it is
+ * and the `flagOf(behavior.destructive, false)` below — the collision-breaker, not a second reading
+ * of the wire — answers `false` for it. Were `{}` read as a plain write, every such action would
+ * report as classified and the coverage warning would miss exactly the actions it exists to name,
+ * the same way `AirtableApi`'s `metadata: null` actions would.
+ *
+ * THE CLOSED VERDICT IS ALSO A REAL ANSWER, which is the only subtlety here: `Attio.RemoveFromList`
+ * genuinely publishes `destructive: true` and lands on the same pair. So the verdict is read first,
+ * and only that one collision is broken — by asking whether Arcade actually sent a `behavior` whose
+ * `destructive` flag reads true, through `flagOf`, the same function `classify` reads it with and
+ * never a second parse of the wire.
+ *
+ * NEVER THE `Api` SUFFIX (spec D8). `AirtableApi` and `GithubApi` are unannotated today and
+ * `Apollo` is annotated, so the suffix correlates beautifully — and it is Arcade's naming
+ * convention rather than a contract. A rule reading it is a guess wearing a measurement's clothes,
+ * right until Arcade curates `DatadogApi`.
+ */
+function isClassified(metadata: unknown): boolean {
+ const verdict = classify(metadata);
+ if (
+ verdict.effect !== CLOSED_VERDICT.effect ||
+ verdict.destructive !== CLOSED_VERDICT.destructive
+ ) {
+ return true;
+ }
+ const behavior = record(record(metadata)?.behavior);
+ return behavior !== null && flagOf(behavior.destructive, false) === true;
+}
+
+/**
+ * The oauth2 scopes one listed action asks for, for the catalogue's counts — read through
+ * {@link requiredScopesOf}, the one reading `grantStateOf` and the transport's listing share.
+ *
+ * AN UNREADABLE SCOPES BLOCK REFUSES, as {@link requiresAuthorization} refuses an unreadable
+ * authorization block, and for its reason: `[]` here files the action as unscoped — "the connection
+ * itself is the grant" — so a block this deployment could not read would be counted as the vendor
+ * saying no separate permission exists. The SHAPE (`authorizationRequirementOf`, `shapeOf`) lives in
+ * `./arcade` because the transport's own listing reads the same block for the scopes the gate checks.
+ */
+function scopesOf(item: unknown, toolkit: string): string[] {
+ const read = requiredScopesOf(item);
+ if (read.kind === "read") return read.scopes;
+ throw new BrokerRefusalError(
+ `Arcade sent ${read.shape} where the ${read.field === "requirements" || read.field === "authorization" ? read.field : `oauth2 ${read.field}`} of one of ${toolkit}'s actions belongs, and that is what says which permissions connecting ${toolkit} asks for. A shape this deployment cannot read is not "no separate permission", so the catalogue stops here rather than count it as one. That is a change in what Arcade answers rather than a setting an operator can correct, so upgrading this deployment's @arcadeai/arcadejs is what fixes it.`,
+ );
+}
+
+/**
+ * Whether ARCADE SAYS this action needs a person to authorize it — read, never assumed.
+ *
+ * THIS REPLACES A HARDCODED `{ kind: "consent" }` THAT WAS A CLAIM ABOUT THE VENDOR MADE WITHOUT
+ * READING THE VENDOR. Every toolkit in the catalogue was announced to the rest of this deployment
+ * as an app somebody connects by clicking through a consent screen, and the live listing says
+ * otherwise about real apps: `Apollo` publishes NO `requirements.authorization` and one
+ * deployment-wide secret (`APOLLO_API_KEY`) instead — verbatim in `fixtures/arcade-catalogue.json`
+ * — so Connect sent somebody to a consent flow for a connection consent cannot complete, and
+ * `tools.authorize` was minted for an action that requires no authorization at all.
+ *
+ * AN AUTHORIZATION BLOCK IS THE WHOLE OF THE QUESTION, AND SECRETS ARE NOT PART OF IT. The two are
+ * different people's work and {@link grantStateOf} already keeps them apart for the same reason:
+ * a secret is set ONCE FOR EVERYBODY by an operator in the Arcade dashboard — {@link FIELDS_REFUSAL}
+ * is this deployment's standing sentence about that — so it is not something the person connecting
+ * can supply, and an app that asks only for one asks THEM for nothing. It is therefore `no-auth`
+ * here and `needs-operator` on the grants screen, which is the channel that already names the keys.
+ * Calling it `fields` would draw a form collecting one person's answer and share it with everyone;
+ * calling it `unsupported` would hide the app from the picker for good, including after the
+ * operator sets the secret and the app works perfectly.
+ *
+ * ANY ONE ACTION'S AUTHORIZATION MAKES THE TOOLKIT A CONSENT APP, because the question this answers
+ * is "how would somebody connect this app", and a toolkit with one authorized action has a consent
+ * flow that is how they reach it. Arcade grants per action, so the grants screen is where an action
+ * that needs nothing is told apart from one that does.
+ *
+ * AND A SHAPE THIS DEPLOYMENT CANNOT READ REFUSES RATHER THAN ANSWERING EITHER WAY. Both wrong
+ * answers are silent: a guessed `consent` is the defect above, and a guessed `no-auth` records an
+ * app as needing no connection, which hides the Connect button on an app that does. `routes.ts`
+ * holds the catalogue for the picker and the enable path both, so one refusal an operator can act
+ * on is worth more than several hundred rows, one of which is a guess — `connectionOf` in
+ * `composio-adapter.ts` gives that reasoning in full. `null` and an absent field are NOT unreadable
+ * shapes: they are the vendor saying this action requires nothing, which `AirtableApi`'s
+ * `metadata: null` shows Arcade does say.
+ */
+function requiresAuthorization(item: unknown, toolkit: string): boolean {
+ const read = authorizationRequirementOf(item);
+ if (read.kind === "unreadable" && read.field === "requirements") {
+ throw new BrokerRefusalError(
+ `Arcade sent ${read.shape} where ${toolkit}'s requirements belong, and that field is what says whether somebody has to authorize this app at all. A shape this deployment cannot read is not the vendor saying "nothing to authorize", so it is not read as one — the catalogue stops here rather than offer ${toolkit} under a flow nobody established it has. That is a change in what Arcade answers rather than a setting an operator can correct, so upgrading this deployment's @arcadeai/arcadejs is what fixes it.`,
+ );
+ }
+ if (read.kind === "unreadable") {
+ throw new BrokerRefusalError(
+ `Arcade sent ${read.shape} where ${toolkit}'s authorization requirement belongs, and this deployment reads that object to decide whether connecting ${toolkit} means sending somebody to a consent screen. A shape this deployment cannot read decides neither way, so the catalogue stops here rather than guess. That is a change in what Arcade answers rather than a setting an operator can correct, so upgrading this deployment's @arcadeai/arcadejs is what fixes it.`,
+ );
+ }
+ return read.kind === "required";
+}
+
+/**
+ * The flow an app gets, from the one fact {@link requiresAuthorization} measured over its actions.
+ *
+ * TWO OF THE FIVE KINDS AND NOT THE OTHER THREE, deliberately. `fields` and `self-registering` are
+ * Composio's shapes: Arcade has no per-person field entry ({@link FIELDS_REFUSAL}) and registers no
+ * client at consent time. `unsupported` is a verdict about an app this deployment cannot drive, and
+ * both answers here are apps it drives — the unreadable case REFUSES rather than landing here.
+ */
+function connectionOf(authorizes: boolean): BrokerConnection {
+ return authorizes ? { kind: "consent" } : { kind: "no-auth" };
+}
+
+/**
+ * Every toolkit Arcade publishes, with `actionCount` taken from the FULL PAGED tool listing.
+ *
+ * COUNTED, NEVER READ OFF PAGE ONE: `GithubApi` publishes hundreds of actions where `Github`
+ * publishes 43, so a first page of 100 would be a wrong answer in the picker. Pagination ends on a
+ * short page; `total_count` is optional on the wire and is used only as a cross-check.
+ *
+ * THE OTHER THREE FACTS ARE COUNTED IN THIS SAME FOLD, BEFORE THE TOOLS ARE DISCARDED, and that is
+ * the whole of why they are free: the listing is already paged to the end for `actionCount`, so no
+ * second listing and NO EXTRA VENDOR CALL is made for them.
+ *
+ * `permissionCount` is the number of DISTINCT NON-EMPTY scope sets, held per toolkit as sorted,
+ * joined strings — SORTED BEFORE JOINED, so `[a,b]` and `[b,a]` are one permission asked for twice.
+ * An action with NO scopes joins no group: Github's 43 actions all carry an `oauth2` block with no
+ * scopes and one read consent flipped 43 of 43 to `met: true`, so "no scopes" means "the connection
+ * itself is the grant", not "needs nothing" — such an action is not separately approvable.
+ * `scopedActionCount` is what keeps that from reading as one permission covering everything.
+ */
+async function listApps(client: Arcade): Promise {
+ /**
+ * The fold's own tally, with PLAIN NUMBERS. `BrokerApp`'s three counts are nullable because
+ * another provider may not measure them; this provider does, on every action it folds, so a
+ * nullable accumulator here would only be a null nothing can produce, narrowed at every `+= 1`.
+ * `permissions` holds the sorted-and-joined scope sets; empty sets never enter it.
+ */
+ type Tally = Omit<
+ BrokerApp,
+ "classifiedCount" | "connection" | "permissionCount" | "scopedActionCount"
+ > & {
+ classifiedCount: number;
+ scopedActionCount: number;
+ permissions: Set;
+ /**
+ * Whether any action folded so far carries an authorization requirement — the ONE fact
+ * {@link connectionOf} turns into this app's flow, and a fold rather than a literal because the
+ * listing is per ACTION and the flow is per TOOLKIT.
+ */
+ authorizes: boolean;
+ };
+ const counts = new Map();
+ for await (const item of pagedItems({
+ pageSize: DIRECTORY_PAGE_SIZE,
+ maxPages: CATALOGUE_MAX_PAGES,
+ concurrency: DIRECTORY_CONCURRENCY,
+ page: (offset) =>
+ vendorCall("the app catalogue listing", () =>
+ client.tools.list({ limit: DIRECTORY_PAGE_SIZE, offset }),
+ ),
+ malformed:
+ "Arcade did not answer with a tool list, so its app catalogue could not be read.",
+ unending: `Arcade's catalogue never ended after ${CATALOGUE_MAX_PAGES} pages, so it is not trusted.`,
+ incomplete: (seen, total) =>
+ `Arcade listed ${seen} of ${total} actions, so its app catalogue is incomplete.`,
+ unreadableTotal: (shown) =>
+ `Arcade reported its app catalogue's total_count as ${shown}, which is not a count, so whether the catalogue was listed in full could not be checked.`,
+ })) {
+ const toolkit = (
+ item as { toolkit?: { name?: unknown; description?: unknown } } | null
+ )?.toolkit;
+ // AN EMPTY NAME IS NO NAME, the same judgement {@link textOf} makes: the name is this app's
+ // SLUG as well as its label, so an action whose toolkit is named `""` would otherwise raise a
+ // blank, unconnectable row in the picker rather than being skipped as unreadable.
+ const name = textOf(toolkit?.name);
+ if (!name) continue;
+ const classified = isClassified(record(item)?.metadata);
+ // READ OFF THIS ACTION, never written down for the catalogue; see {@link requiresAuthorization}.
+ // Asked AFTER the name, so a refusal can say which app it is about, and BEFORE the scopes, so an
+ // unreadable authorization block is refused in that function's words rather than the scopes'.
+ const authorizes = requiresAuthorization(item, name);
+ const scopes = scopesOf(item, name);
+ const standing =
+ counts.get(name) ??
+ ({
+ slug: name,
+ name,
+ // An absent description IS an empty one here: the field is a label with a blank default,
+ // not an identity, so nothing is shadowed by the two reading alike.
+ description: textOf(toolkit?.description) ?? "",
+ // NEITHER IS A STAND-IN FOR SOMETHING UNREAD: a tool's `toolkit` object carries `name`,
+ // `description` and `version` and nothing else — the SDK's `ToolDefinition.Toolkit` says
+ // so and the live listing agrees — so these two are Arcade publishing no logo and no
+ // category, which is what `BrokerApp.logo` is nullable for. A logo, where one exists, is
+ // filled in afterwards by `backfillBrokeredLogos` from outside the vendor's listing.
+ logo: null,
+ categories: [],
+ actionCount: 0,
+ classifiedCount: 0,
+ scopedActionCount: 0,
+ permissions: new Set(),
+ authorizes: false,
+ } satisfies Tally);
+ standing.actionCount += 1;
+ if (classified) standing.classifiedCount += 1;
+ if (authorizes) standing.authorizes = true;
+ if (scopes.length > 0) {
+ standing.scopedActionCount += 1;
+ // SORTED, THEN JOINED. The other order makes one permission into two the first time the
+ // vendor lists the same scopes in a different sequence.
+ standing.permissions.add([...scopes].sort().join(" "));
+ }
+ counts.set(name, standing);
+ }
+ // `permissionCount` IS READ OFF THE FINISHED SET, never kept per page: a size taken mid-listing
+ // is a count of the sets seen so far, which is a different number on every page but the last.
+ // `connection` IS READ OFF THE FINISHED FOLD for `permissionCount`'s reason: a kind decided
+ // mid-listing is a verdict about the actions seen so far, and the action that carries the
+ // authorization may be on the last page.
+ return [...counts.values()].map(({ permissions, authorizes, ...app }) => ({
+ ...app,
+ permissionCount: permissions.size,
+ connection: connectionOf(authorizes),
+ }));
+}
+
+const FIELDS_REFUSAL =
+ "Arcade has no per-person field entry: what it asks for is a deployment-wide secret (admin.secrets, e.g. GITHUB_SERVER_URL) that an operator sets once for everybody, so a form asking a person to type one would collect a value that is not theirs and share it with everyone. Set it in the Arcade dashboard instead.";
+
+type ConnectionRow = Arcade.Admin.UserConnectionResponse;
+
+/** Page size for the auth-provider configuration listing. A short page ends it, as `listApps`. */
+const AUTH_PROVIDER_PAGE_SIZE = 100;
+/** A runaway guard, not a bound. */
+const AUTH_PROVIDER_MAX_PAGES = 100;
+
+/** Page size for the user-connection listing. A short page ends it, as `listApps`. */
+const CONNECTION_PAGE_SIZE = 100;
+/** A runaway guard, not a bound. */
+const CONNECTION_MAX_PAGES = 100;
+
+/**
+ * The INSTANCE ids of every auth provider this project has configured of one TYPE.
+ *
+ * THERE ARE THREE THINGS AND THEY NEED THREE WORDS. `provider` in this codebase is the BROKER
+ * (`arcade`); `authProvider` is the vendor's OAuth provider TYPE (`google`); and this is the third,
+ * the provider INSTANCE — an operator's configuration OF a type, which Arcade keys by its own id
+ * (`google-openbot`, `arcade-google`, `cpk-poc`). On an auth-provider config `id` is the INSTANCE
+ * and `provider_id` is the TYPE. Verbatim from the live project:
+ *
+ * ```
+ * {"id":"cpk-poc", "provider_id":"github", "type":"oauth2", "status":"active"}
+ * {"id":"google-openbot", "provider_id":"google", "type":"oauth2", "status":"active"}
+ * {"id":"arcade-google", "provider_id":"google", "type":"oauth2", "status":"active"}
+ * ```
+ *
+ * A SET AND NEVER ONE ID: `arcade-google` and `google-openbot` are both of type `google`, and a
+ * person may hold a connection under either. Reducing to one would drop real connections.
+ *
+ * PAGED, AND A MALFORMED LISTING THROWS, on `listApps`' argument and with its guards: a truncated
+ * config list would silently answer "not connected" for whoever is held under an instance that fell
+ * off the end. Folded to lower case on the way in, so the comparison is Amendment 16's.
+ */
+async function authProviderInstances(
+ client: Arcade,
+ authProvider: string,
+): Promise> {
+ const wanted = authProvider.toLowerCase();
+ const instances = new Set();
+ for await (const item of pagedItems({
+ pageSize: AUTH_PROVIDER_PAGE_SIZE,
+ maxPages: AUTH_PROVIDER_MAX_PAGES,
+ page: (offset) =>
+ vendorCall(
+ `the auth-provider configuration listing for ${authProvider}`,
+ () =>
+ client.admin.authProviders.list({
+ query: { limit: AUTH_PROVIDER_PAGE_SIZE, offset },
+ }),
+ ),
+ malformed: `Arcade did not answer with an auth-provider list, so the configurations of ${authProvider} its connections are held under could not be read.`,
+ unending: `Arcade's auth-provider list never ended after ${AUTH_PROVIDER_MAX_PAGES} pages, so it is not trusted.`,
+ incomplete: (seen, total) =>
+ `Arcade listed ${seen} of ${total} auth providers, so its configuration list is incomplete.`,
+ unreadableTotal: (shown) =>
+ `Arcade reported its auth-provider list's total_count as ${shown}, which is not a count, so whether the configurations of ${authProvider} were listed in full could not be checked.`,
+ })) {
+ const config = record(item);
+ const instance = textOf(config?.id);
+ if (instance && textOf(config?.provider_id)?.toLowerCase() === wanted) {
+ instances.add(instance.toLowerCase());
+ }
+ }
+ return instances;
+}
+
+/**
+ * The connections an account holds under a toolkit's AUTH PROVIDER.
+ *
+ * FILTERED ON THE PROVIDER, NOT THE TOOLKIT NAME — AND NOT ON THE PROVIDER TYPE EITHER. Both halves
+ * of that are measured, and the second is why this reads a third listing rather than comparing two
+ * strings.
+ *
+ * THE TOOLKIT NAME IS THE WRONG SPELLING because a provider equals the toolkit only when an
+ * operator happened to name it so (`Github`, `Asana`, `Attio`); one Google connection serves
+ * `Gmail` and `GoogleCalendar` both. So the toolkit is resolved through `authProviderOf`, which is
+ * where that mapping lives.
+ *
+ * AND `authProviderOf`'S ANSWER IS ALSO THE WRONG SPELLING FOR A CONNECTION ROW, because
+ * `provider_id` MEANS TWO DIFFERENT THINGS ON THE TWO OBJECTS. Measured against the live API:
+ *
+ * - on a tool listing, `requirements.authorization.provider_id` — what `authProviderOf` reads — is
+ * the provider TYPE: `"google"`;
+ * - on a user-connection row, `provider_id` is the provider INSTANCE id, the operator's
+ * configuration: `"google-openbot"`, `"arcade-google"`, `"cpk-poc"`.
+ *
+ * A connection row's full key set, verbatim, carries NO vendor-type field at all: `id`,
+ * `provider_type`, `connection_id`, `connection_status`, `scopes`, `user_id`, `provider_id`,
+ * `provider_description`, `provider_user_info` — and `provider_type` is `"oauth2"`, the auth SCHEME
+ * rather than the vendor. The instance id is the ONLY link to the vendor. So `"google"` never
+ * equals `"google-openbot"`, a direct comparison matches nothing on any real deployment — with
+ * Arcade's own default providers too, which are `arcade-google` and `arcade-github` and never the
+ * bare type — and `isConnected` reads false, `accountName` null, and `revoke` false WITHOUT
+ * SPENDING A DELETE, so nothing is ever revoked at the vendor. Do not "simplify" this back: every
+ * fixture that spells the two sides alike hides it.
+ *
+ * The bridge is {@link authProviderInstances}: the type `authProviderOf` answered is expanded, once
+ * per call, into the SET of configured instance ids of that type, and a row matches when its
+ * `provider_id` is in that set (case-insensitively — Amendment 16's client-side fold stands). Both
+ * the provider and the configuration list are read ONCE per call, never per row.
+ *
+ * PAGED THROUGH {@link pagedItems}, NEVER THROUGH THE SDK'S `for await (… of … .list(…))`. That
+ * iterator asks `OffsetPage.nextPageRequestOptions`, which returns `null` the moment `total_count`
+ * is falsy — and the constructor coerces an ABSENT `total_count` to `0`. So against any engine that
+ * omits the field, the SDK's own auto-pagination stops after page one: measured at 30 connections
+ * served 10 to a page, ONE request went out, `isConnected` answered `true`, `accountName` read a
+ * name off page one, and `revoke` DELETED TEN AND ANSWERED `true` while twenty connections stayed
+ * live at the vendor. That is the outcome `revoke`'s own comment forbids — a partial ask reading as
+ * a clean success. Reading to a SHORT PAGE instead ends on evidence the engine cannot omit, and an
+ * unverifiable listing THROWS rather than answering short, so no caller here can mistake a first
+ * page for the whole of what somebody holds.
+ *
+ * A ROW UNDER AN INSTANCE THE CONFIG LIST DOES NOT NAME DOES NOT MATCH — a provider configuration
+ * deleted while a connection outlives it. That is fail-closed: the row is left alone rather than
+ * deleted on a guess about which vendor it belongs to.
+ *
+ * EVERY ATTRIBUTABLE ROW IS RETURNED, DEAD ONES INCLUDED, AND LIVENESS IS THE CALLER'S QUESTION.
+ * A row carries its own `connection_status`, and {@link connectionIsLive} reads it — but here,
+ * because the two kinds of caller need opposite things from a row the vendor reports as finished:
+ * `isConnected` and `accountName` must not count it, and `revoke` must still delete it. Filtering it
+ * out at this level would answer both at once, and the one it answered wrongly would be the
+ * destructive one.
+ *
+ * AN UNRESOLVED TOOLKIT REFUSES, AND DOES NOT FALL BACK TO THE TOOLKIT NAME. The fallback is
+ * refused for the reason it always was — it would silently restore the instance/type defect under
+ * a different spelling, invisibly against every fixture in the tree. But ANSWERING NO ROWS IS NOT
+ * THE OTHER OPTION, and that is this function's own measured defect. A toolkit whose actions name
+ * ZERO provider ids, or TWO OR MORE of them, is not a toolkit with no connections: it is a listing
+ * this deployment could not read an attribution out of. Answering `[]` for it made `isConnected`
+ * false and `accountName` null for somebody genuinely connected — so Connect minted a SECOND
+ * authorization beside the live one — and made `revoke` answer `false` ("there was nothing to
+ * withdraw") WITHOUT SPENDING A SINGLE DELETE, while `store.ts`'s disconnect path deletes the
+ * local `brokered_connections` row on that answer regardless. The outcome is the one
+ * `broker.ts`'s `revoke` contract forbids outright: a LIVE grant at the vendor that no row this
+ * deployment holds any longer names, and no way back but guessing which app it belonged to.
+ *
+ * So an unattributable toolkit throws a {@link BrokerRefusalError} naming what the listing said,
+ * exactly as a malformed one does — `authProviderOf` and {@link authProviderInstances} THROW on a
+ * listing that is malformed, truncated against its own reported total, or never-ending, and this
+ * joins them. All three lifecycle members refuse together, which is the point: the refusal reaches
+ * the Disconnect path BEFORE any local row is deleted, so the row is still standing on the second
+ * press, and the Connect screen says why rather than offering a second grant.
+ *
+ * THE SEAM'S `authProviderOf` STILL ANSWERS `null`, and that is not the same decision. Its caller
+ * is the sibling resolver in `store.ts`, which counts an unresolved app as `unanswered` and ends
+ * NOTHING for it — fail-closed on a question that spends no delete. Here the question is the
+ * destructive one.
+ */
+async function connectionsFor(
+ client: Arcade,
+ vendorUserId: string,
+ toolkit: string,
+): Promise {
+ const { named, unreadable } = await authProvidersNamedBy(client, toolkit);
+ // FIRST, because an unreadable action makes both counts below unreliable: it may name none, the
+ // one already read, or another.
+ if (unreadable) {
+ throw new BrokerRefusalError(
+ unreadableProviderSentence(toolkit, unreadable),
+ );
+ }
+ if (named.size !== 1) {
+ throw new BrokerRefusalError(
+ named.size === 0
+ ? `Arcade's action listing for ${toolkit} names no auth provider at all, so which of this account's connections belong to ${toolkit} could not be read. Nothing was read as connected and nothing was withdrawn — a connection may still be live at the vendor. Give ${toolkit} an auth provider in the Arcade dashboard and try again.`
+ : `Arcade's action listing for ${toolkit} names ${named.size} different auth providers (${[...named].sort().join(", ")}), so which of this account's connections belong to ${toolkit} could not be read. Nothing was read as connected and nothing was withdrawn — a connection may still be live at the vendor. Settle ${toolkit} on one auth provider in the Arcade dashboard and try again.`,
+ );
+ }
+ // The one id, taken off the set the refusal above has already narrowed to exactly one.
+ const [authProvider] = [...named];
+ const instances = await authProviderInstances(client, authProvider);
+ const rows: ConnectionRow[] = [];
+ for await (const item of pagedItems({
+ pageSize: CONNECTION_PAGE_SIZE,
+ maxPages: CONNECTION_MAX_PAGES,
+ page: (offset) =>
+ vendorCall(`the connection listing for ${toolkit}`, () =>
+ client.admin.userConnections.list({
+ user_id: vendorUserId,
+ limit: CONNECTION_PAGE_SIZE,
+ offset,
+ }),
+ ),
+ malformed: `Arcade did not answer with a connection list, so what this account holds at ${toolkit} could not be read.`,
+ unending: `Arcade's connection list never ended after ${CONNECTION_MAX_PAGES} pages, so it is not trusted.`,
+ incomplete: (seen, total) =>
+ `Arcade listed ${seen} of ${total} connections, so what this account holds at ${toolkit} could not be read in full.`,
+ unreadableTotal: (shown) =>
+ `Arcade reported its connection list's total_count as ${shown}, which is not a count, so whether what this account holds at ${toolkit} was listed in full could not be checked.`,
+ })) {
+ /*
+ * A ROW THAT IS NOT AN OBJECT IS A HOLE IN THE LISTING, AND A HOLE IS NOT A ROW TO SKIP.
+ *
+ * `items: [null]` is what the live API sent here, and `item as ConnectionRow` believed it: the
+ * very next line read `row.provider_id` off `null` and raised a bare `TypeError`, which is the
+ * wrong answer in both directions. It carries a stack trace onto the Disconnect path where
+ * this deployment's own sentence belongs; and the only reason the revoke invariant held on
+ * this shape was that a crash is technically "not answering" — make the cast defensive with a
+ * skip or a `?? []` and the listing silently becomes two rows instead of three, which is
+ * `revoke` answering off a listing it could not read whole, the exact outcome
+ * `broker.ts:712-717` forbids.
+ *
+ * SO IT REFUSES, in the register of this listing's own `malformed` sentence one block up: the
+ * unreadable row is not attributable to any provider instance, and a DELETE spent on the rows
+ * BESIDE it would be a partial ask reported as a clean success.
+ */
+ // ARRAYS ARE REFUSED WITH THE REST, which {@link record} alone would not do — it answers a
+ // list as an object, and a list has no `provider_id`, so the row would be dropped silently
+ // and the listing would again be read short.
+ const row = Array.isArray(item) ? null : record(item);
+ if (!row) {
+ throw new Error(
+ `Arcade's connection list carried ${shapeOf(item)} where a connection belongs, so what this account holds at ${toolkit} could not be read.`,
+ );
+ }
+ const instance = textOf(row.provider_id)?.toLowerCase();
+ if (instance !== undefined && instances.has(instance))
+ rows.push(row as ConnectionRow);
+ }
+ return rows;
+}
+
+/**
+ * The one `connection_status` measured on a usable connection at the live API: `"active"`.
+ *
+ * AN ALLOWLIST OF THE LIVE SPELLING RATHER THAN A DENYLIST OF THE DEAD ONES, because the dead
+ * vocabulary cannot be enumerated from here — the SDK declares the field as a bare
+ * `connection_status?: string` with no union, and a denylist missing one spelling would go on
+ * reading that spelling as connected, which is the defect itself.
+ */
+const LIVE_CONNECTION_STATUS = "active";
+
+/**
+ * The `status` spellings a FRESH MINT may answer under which sending somebody to the consent screen
+ * is the right next step: the flow has not begun (`not_started`) or is waiting for them (`pending`).
+ *
+ * AN ALLOWLIST, AND NOT THE SDK'S UNION TURNED AROUND, for {@link LIVE_CONNECTION_STATUS}'s reason.
+ * `AuthorizationResponse.status` is declared as a four-way union in the generated types, but the
+ * union is a description of what Arcade sends today, not a constraint on what arrives: a spelling
+ * outside it deserializes with no complaint from anybody. The two outcomes that are NOT a consent
+ * screen — `completed` and `failed` — are therefore refused by absence rather than by name, so that
+ * a fifth spelling is refused with them instead of being handed to a browser as a live redirect.
+ *
+ * SEPARATE FROM {@link grantStateOf}'S READING, deliberately. That one reads the LISTING's
+ * `requirements.authorization`, whose vocabulary is `token_status` plus an `active`/`inactive`
+ * status, and whose question is "what does this account already hold". This one reads a mint's own
+ * answer, whose question is "is a consent screen what happens next". Sharing a table between them
+ * would make one vendor's change to either vocabulary move the other answer too.
+ */
+const MINT_AWAITS_CONSENT_STATUSES = new Set(["not_started", "pending"]);
+
+/**
+ * The listing's `token_status` spellings under which this account holds no usable token and a
+ * consent is what would give it one. `completed` is the held grant; anything else is `unknown`.
+ * Kept apart from {@link MINT_AWAITS_CONSENT_STATUSES} for the reason that set's docblock gives,
+ * even where the spellings overlap: `failed` belongs here (a failed token is one to consent again
+ * for) and not there (a failed MINT is not a consent screen to send somebody to).
+ */
+const TOKEN_AWAITS_CONSENT_STATUSES = new Set([
+ "not_started",
+ "pending",
+ "failed",
+]);
+
+/**
+ * Whether a connection row is one somebody can actually USE, read off the row's OWN `connection_status`.
+ *
+ * THE ROW'S EXISTENCE IS NOT THE ANSWER, WHICH IS WHAT THIS IS FOR. A user-connection row carries
+ * `connection_status` beside its ids — it is in the key set {@link connectionsFor} quotes verbatim
+ * — and that field is the vendor saying whether the connection still works. Treating a row's mere
+ * presence as a yes answers `isConnected: true`, with a name beside it, for a connection Arcade is
+ * reporting as expired, revoked at the provider, pending or failed. Nothing then goes wrong until a
+ * tool call fails, far from the screen that said Connected, with no way back but guessing.
+ *
+ * SILENCE IS NOT A NO. An ABSENT status — the key missing, `undefined` or `null` — reads as live,
+ * deliberately: the field is OPTIONAL on the wire (`connection_status?: string`), so a pessimistic
+ * reading would answer "not connected" for every real connection against any engine that omits it
+ * — `isConnected` false and `accountName` null — which is precisely the live defect this seam
+ * already suffered once when it compared a provider TYPE to a provider INSTANCE. The rule is
+ * therefore: the vendor said something other than live → not live; the vendor said nothing → the
+ * existence of the row is all the evidence there is, and it stands.
+ *
+ * BUT A STATUS THAT IS PRESENT AND CANNOT BE READ IS NOT SILENCE. `{}`, `false`, `0`, a list, or a
+ * BLANK string is the vendor saying SOMETHING about this connection in a shape this deployment does
+ * not read — and "live" is the one answer that would act on it. It reads as not live, by
+ * {@link vendorStatusOf}'s rule: a blank string on a field whose value is a decision is unreadable,
+ * the judgement `authProvidersNamedBy` already makes of a blank `provider_id`. (This file once said
+ * "absent or empty reads as live"; the empty half is withdrawn, so that one rule — absent is
+ * `undefined`/`null`, blank is unreadable — holds on every vendor authorization field.)
+ *
+ * FOLDED AND TRIMMED, Amendment 16's client-side fold, for the reason every other comparison in
+ * this file is: a case the vendor changes must not change an answer about somebody's account.
+ *
+ * NOT A FILTER ON {@link connectionsFor}. `revoke` must still delete a dead row — it is a row at the
+ * vendor, and leaving it behind as unreachable debris while answering `false` ("there was nothing")
+ * is the failure mode `revoke`'s own contract forbids. So the listing stays whole and only the
+ * POSITIVE reads — `isConnected`, `accountName` — ask this question.
+ */
+function connectionIsLive(row: ConnectionRow): boolean {
+ const status = vendorStatusOf(row.connection_status);
+ if (status.kind === "absent") return true;
+ return status.kind === "text" && status.text === LIVE_CONNECTION_STATUS;
+}
+
+/**
+ * DELETE TAKES `connection_id` (`ac_...`), NOT `id` (`at_...`). A user-connection row carries both
+ * and the listing prints `id` FIRST; the SDK declares `delete(id: string)` and does not say which.
+ * Measured against the live API: `at_...` answers 500, `ac_...` answers 204. Reading the obvious
+ * field fails only when somebody presses Disconnect, the worst time to find out.
+ */
+async function deleteConnection(
+ client: Arcade,
+ connectionId: string | undefined,
+): Promise {
+ if (!connectionId) {
+ throw new Error("Arcade connection has no connection_id to delete.");
+ }
+ await vendorCall("a connection delete", () =>
+ client.admin.userConnections.delete(connectionId),
+ );
+}
+
+const record = (value: unknown): Record | null =>
+ value !== null && typeof value === "object"
+ ? (value as Record)
+ : null;
+
+/**
+ * One listed action read into this deployment's vocabulary, or nothing when it has no name.
+ *
+ * `requirements.met` IS A BOOLEAN WITH TWO UNRELATED CAUSES AND IS NEVER READ HERE. It is false
+ * both when the person has not consented and when a deployment-wide secret is unset — the live
+ * listing shows `Github` requiring `GITHUB_SERVER_URL`, which an operator sets once for everybody
+ * and which no amount of consenting moves. Sending somebody to a consent screen over that one
+ * sends them somewhere that cannot help them, so the secrets are read FIRST and answer
+ * `needs-operator` with the keys they name.
+ *
+ * `unknown` IS ITS OWN STATE RATHER THAN A PESSIMISTIC `needs-consent`, because not knowing is not
+ * the same as not connected: telling somebody to authorize something they already authorized sends
+ * them round twice. So anything the listing does not say clearly answers `unknown` with the reason.
+ */
+function grantStateOf(item: unknown): GrantState[] {
+ const tool = record(item);
+ const action = textOf(tool?.qualified_name);
+ if (!action) return [];
+ const requirements = record(tool?.requirements);
+
+ /*
+ * A SECRETS FIELD THAT IS NOT A LIST IS UNREADABLE, NOT EMPTY — the same judgement `classify`
+ * makes about an empty `behavior`, one requirement description along. `secrets` absent is the
+ * ordinary case and genuinely means this action needs no deployment secret; `secrets: {}`, or any
+ * other non-list, is Arcade describing requirements in a shape this cannot read, and silently
+ * folding it to "no secrets, none missing" lets the function go on to answer `authorized` for an
+ * action that may be waiting on an unset `GITHUB_SERVER_URL`. Nobody is then told to set it: the
+ * `needs-operator` state that exists to name it cannot fire, and the person is left with an
+ * action the screen says they hold and the vendor refuses. `unknown` with a reason is what the
+ * docblock's "anything the listing does not say clearly" already promises.
+ */
+ const rawSecrets = requirements?.secrets;
+ if (
+ rawSecrets !== undefined &&
+ rawSecrets !== null &&
+ !Array.isArray(rawSecrets)
+ ) {
+ return [
+ {
+ action,
+ state: "unknown",
+ reason:
+ "Arcade listed this action's deployment secrets as something other than a list, so whether any is unset could not be read.",
+ },
+ ];
+ }
+ const missing: string[] = [];
+ let secretUnreadable = false;
+ for (const raw of Array.isArray(rawSecrets) ? rawSecrets : []) {
+ const secret = record(raw);
+ const key = textOf(secret?.key);
+ if (!key) {
+ secretUnreadable = true;
+ continue;
+ }
+ if (secret?.met === false) missing.push(key);
+ else if (secret?.met !== true) secretUnreadable = true;
+ }
+ if (missing.length > 0) return [{ action, state: "needs-operator", missing }];
+ if (secretUnreadable) {
+ return [
+ {
+ action,
+ state: "unknown",
+ reason:
+ "Arcade listed a deployment secret for this action without saying whether it is set.",
+ },
+ ];
+ }
+
+ // NO AUTHORIZATION BLOCK IS THE VENDOR SAYING THIS ACTION NEEDS NONE, with every secret it does
+ // need already read as set above — so it is usable, and `authorized` with no scopes is the state
+ // that says so. See {@link authorizationRequirementOf} for why this is the one reading.
+ // `not-required`, NEVER `held`: nobody approved it and no approval reaches it, so the consent
+ // copy must not count it among what an approval approves. See {@link GrantState}.
+ const read = authorizationRequirementOf(item);
+ if (read.kind === "none") {
+ return [
+ {
+ action,
+ state: "authorized",
+ scopes: [],
+ authorization: "not-required",
+ },
+ ];
+ }
+ if (read.kind === "unreadable") {
+ return [
+ {
+ action,
+ state: "unknown",
+ reason:
+ read.field === "requirements"
+ ? `Arcade listed this action's requirements as ${read.shape}, so what this account holds could not be read.`
+ : `Arcade listed this action's authorization as ${read.shape}, so what this account holds could not be read.`,
+ },
+ ];
+ }
+ // THE SCOPES BY THE ONE READING, see {@link requiredScopesOf}: an unreadable `oauth2` or
+ // `scopes` is `unknown`, never `[]` — `authorized []` is "the connection is the whole grant",
+ // which is an answer about this account the listing did not give.
+ const scopesRead = requiredScopesOf(item);
+ if (scopesRead.kind === "unreadable") {
+ return [
+ {
+ action,
+ state: "unknown",
+ reason: `Arcade listed the permissions this action needs as ${scopesRead.shape}, so what this account holds could not be read.`,
+ },
+ ];
+ }
+ const { scopes } = scopesRead;
+ /*
+ * `token_status` IS THE WHOLE OF WHAT THIS ACCOUNT HOLDS, AND `status` IS NOT READ HERE AT ALL.
+ * The two sit side by side and answer different questions: `status` (`active`/`inactive`, with a
+ * `status_reason` such as "Provider not found" in `fixtures/arcade-catalogue.json`) is the AUTH
+ * PROVIDER's standing at this deployment, and is the same for everybody; `token_status` is this
+ * account's token. Reading `status: "active"` as a grant when `token_status` was missing marked
+ * EVERY action of a configured provider as held by everyone the listing was asked about (A7c), and
+ * reading `inactive` as "go and consent" sends somebody to a consent screen for a provider that
+ * is not there. So an absent or unreadable `token_status` is `unknown`, with the reason, and the
+ * text is FOLDED AND TRIMMED by {@link vendorStatusOf} as every other vendor status is.
+ */
+ const tokenStatus = vendorStatusOf(read.authorization.token_status);
+ if (tokenStatus.kind !== "text") {
+ return [
+ {
+ action,
+ state: "unknown",
+ reason:
+ tokenStatus.kind === "absent"
+ ? "Arcade's listing did not say whether this account holds a token for this action."
+ : `Arcade's listing gave this account's token status for this action as ${tokenStatus.shape}, so whether it is held could not be read.`,
+ },
+ ];
+ }
+ if (tokenStatus.text === "completed") {
+ return [{ action, state: "authorized", scopes, authorization: "held" }];
+ }
+ if (TOKEN_AWAITS_CONSENT_STATUSES.has(tokenStatus.text)) {
+ return [{ action, state: "needs-consent", scopes }];
+ }
+ return [
+ {
+ action,
+ state: "unknown",
+ reason:
+ "Arcade's listing gave this account's token status for this action in a spelling this deployment does not read.",
+ },
+ ];
+}
+
+/**
+ * What one account holds at one app, action by action.
+ *
+ * THIS IS THE ONE PLACE A `user_id` IS SENT ON A LISTING. The transport's `listTools` sends none
+ * and reads no per-person state off the answer, because a catalogue is the same for everybody and a
+ * per-person listing would make it one person's. Here the question IS about a person, so the
+ * account's vendor id goes out with it.
+ *
+ * PAGED TO THE END, as `listApps` is and for its reason: this answers ONE `GrantState` PER ACTION,
+ * so a single first page would quietly drop every action past it — `GithubApi` publishes hundreds —
+ * and the grants screen would show a partial holding as the whole of one. A short page ends it and
+ * an unverifiable listing throws; see {@link pagedItems}.
+ */
+async function grantsFor(
+ client: Arcade,
+ vendorUserId: string,
+ toolkit: string,
+): Promise {
+ const grants: GrantState[] = [];
+ for await (const item of pagedItems({
+ pageSize: CATALOGUE_PAGE_SIZE,
+ maxPages: CATALOGUE_MAX_PAGES,
+ concurrency: CATALOGUE_CONCURRENCY,
+ page: (offset) =>
+ vendorCall(`the action listing for ${toolkit}`, () =>
+ client.tools.list({
+ toolkit,
+ user_id: vendorUserId,
+ limit: CATALOGUE_PAGE_SIZE,
+ offset,
+ }),
+ ),
+ malformed: `Arcade did not answer with an action list for ${toolkit}, so what this account holds there could not be read.`,
+ unending: `Arcade's action list for ${toolkit} never ended after ${CATALOGUE_MAX_PAGES} pages, so it is not trusted.`,
+ incomplete: (seen, total) =>
+ `Arcade listed ${seen} of ${total} actions for ${toolkit}, so what this account holds there could not be read in full.`,
+ unreadableTotal: (shown) =>
+ `Arcade reported the action list for ${toolkit} with a total_count of ${shown}, which is not a count, so whether what this account holds there was listed in full could not be checked.`,
+ })) {
+ grants.push(...grantStateOf(item));
+ }
+ return grants;
+}
+
+/**
+ * The auth provider a toolkit's connections are held under, read off the LISTING ITSELF.
+ *
+ * READ, NEVER DERIVED FROM THE TOOLKIT NAME. `requirements.authorization.provider_id` is the field
+ * the fixture already carries beside the auth-config `id`, and it names an auth-provider
+ * configuration rather than a toolkit — one Google provider serves `Gmail` and `GoogleCalendar`
+ * both. Reading it is correct under BOTH readings of the field: if it did turn out to equal the
+ * toolkit slug, the listing would simply say so. Comparing it to the toolkit name is correct under
+ * only one, and would be invisible against every fixture in this tree, where `Github`, `Asana` and
+ * `Attio` all happen to be named after themselves. There is NO FALLBACK to the toolkit name.
+ *
+ * PAGED TO ITS END THROUGH {@link pagedItems}, LIKE EVERY OTHER LISTING IN THIS FILE. An earlier
+ * revision read ONE unpaged request here and argued the saving was free: every tool in a toolkit
+ * shares its auth provider, so a further page could only repeat what the first already said. THAT
+ * ARGUMENT IS DISPROVEN, measured against a real HTTP engine — a 150-action `Gmail` listing whose
+ * first 100 actions sit under `google` and whose last 50 under `microsoft` answered `google`, off
+ * the single request `/v1/tools?toolkit=Gmail`. The guard below could not fire, because it was
+ * reading a PREFIX and a prefix agrees with itself. "Every tool shares its provider" is a statement
+ * about what a toolkit ought to be, not about what the vendor sends, and this function's whole job
+ * is to read what the vendor sends. The request also carried NO `limit`, so the window it judged
+ * from was the vendor's default rather than {@link CATALOGUE_PAGE_SIZE} — a number this deployment
+ * never chose and the vendor may change.
+ *
+ * The cost is one extra request only on a toolkit larger than a page, and it buys the same property
+ * `grantsFor` and `connectionsFor` are paged for: a first page can never present as the whole of
+ * one. A TRUNCATED LISTING NOW THROWS here too, rather than resolving a provider off whatever
+ * arrived — the `incomplete` sentence below — which is right for the reason `connectionsFor`'s
+ * comment gives: these three callers spend deletes.
+ *
+ * AND IT GUARDS RATHER THAN ASSUMES. The DISTINCT non-null provider ids across the WHOLE listing
+ * are collected and anything other than exactly one answers `null`, so a toolkit that disagrees
+ * with itself is UNANSWERED rather than silently resolved to whichever action came first. Case is
+ * carried through untouched, so two spellings of one provider count as two and are likewise
+ * unanswered; whether they are the same provider is the caller's comparison, and
+ * `connectionSiblings` folds case for the one caller that makes it. A listing carrying ANY action
+ * whose authorization could not be read is unanswered too, however many ids sit beside it — see
+ * {@link authProvidersNamedBy}.
+ *
+ * `null` IS WHAT UNANSWERED MEANS, AND IT IS NOT AN ANSWER ANY CALLER MAY SPEND. It is not "no
+ * provider", and a toolkit with no provider id anywhere lands on it for the same reason an
+ * ambiguous one does. What a caller must do with it depends on what its own answer costs:
+ *
+ * - {@link connectionsFor} — the connection lifecycle, where the answer spends DELETEs and decides
+ * whether somebody is shown as connected — REFUSES, and reads {@link authProvidersNamedBy}
+ * directly so its sentence can say whether the listing named none or several. Answering no rows
+ * there was the live defect: `revoke` said `false` ("nothing to withdraw") with no delete spent
+ * while the caller deleted the local row anyway, and `isConnected`/`accountName` sent somebody
+ * who is already connected round a second authorization. Its comment has that in full.
+ * - The SEAM member below hands `null` on unchanged, for `store.ts`'s sibling resolver, which
+ * counts an unresolved app as `unanswered` and ends nothing for it. That question spends
+ * nothing, so fail-closed is a real answer there and the seam's `Promise` stands.
+ *
+ * `requirements.met` IS NOT READ HERE EITHER, for `grantStateOf`'s reason: this reads a DIFFERENT
+ * field of the same `requirements.authorization` object.
+ */
+async function authProviderOf(
+ client: Arcade,
+ toolkit: string,
+): Promise {
+ const { named, unreadable } = await authProvidersNamedBy(client, toolkit);
+ // An unreadable block is UNANSWERED, for the reason several ids are: the one id beside it is not
+ // the whole of what the listing says.
+ if (unreadable) return null;
+ return named.size === 1 ? [...named][0] : null;
+}
+
+/**
+ * What a toolkit's listing says about its auth provider: the DISTINCT ids it names, and the first
+ * action whose authorization this deployment could not read, if any.
+ *
+ * `unreadable` IS A THIRD ANSWER, NOT AN EMPTY CONTRIBUTION. An action whose `requirements` or
+ * `requirements.authorization` arrives as a shape this deployment cannot read — or whose
+ * `provider_id` is present and is not a name — might name ANY provider, so the set beside it is
+ * not the whole of what the listing says. {@link authorizationRequirementOf} draws the same line for
+ * the catalogue and the grants screen; this is that line applied to `provider_id`.
+ */
+type AuthProvidersNamed = {
+ named: Set;
+ unreadable: {
+ action: string | undefined;
+ field: "requirements" | "authorization" | "provider_id";
+ shape: string;
+ } | null;
+};
+
+/**
+ * Why a toolkit's provider could not be read off one unreadable action, for a refusal to say.
+ * Names the FIELD and the SHAPE that arrived there, never the value, for {@link shapeOf}'s reason.
+ */
+function unreadableProviderSentence(
+ toolkit: string,
+ unreadable: NonNullable,
+): string {
+ const where = unreadable.action
+ ? `${toolkit}'s action ${unreadable.action}`
+ : `one of ${toolkit}'s actions`;
+ const field =
+ unreadable.field === "provider_id"
+ ? "auth provider id"
+ : unreadable.field === "authorization"
+ ? "authorization requirement"
+ : "requirements";
+ return `Arcade sent ${unreadable.shape} where ${where} carries its ${field}, so which auth provider ${toolkit}'s connections are held under could not be read — that action may name a provider the rest of the listing does not. Nothing was read as connected and nothing was withdrawn — a connection may still be live at the vendor. That is a change in what Arcade answers rather than a setting an operator can correct, so upgrading this deployment's @arcadeai/arcadejs is what fixes it.`;
+}
+
+/**
+ * The DISTINCT auth provider ids a toolkit's listing names, read to the listing's end — and the
+ * first action whose authorization could not be read.
+ *
+ * SEPARATED FROM {@link authProviderOf} SO THAT THE VERDICT AND THE EVIDENCE ARE TWO THINGS. The
+ * seam's `authProviderOf` answers a provider or `null`, and `null` is a verdict with the evidence
+ * thrown away — zero ids, nine ids and an unreadable block arrive at its caller as the same word.
+ * {@link connectionsFor} REFUSES on any of them, and a refusal has to say which one happened and
+ * name what it read, so it reads the evidence and `authProviderOf` folds it to its verdict. There is
+ * still ONE reading of the wire.
+ *
+ * AN UNREADABLE BLOCK IS NO LONGER SKIPPED. It once contributed nothing and raised nothing, so a
+ * `Gmail` listing of one `google` action beside one whose authorization arrived as a string
+ * resolved to `google` — a single provider asserted off a listing this deployment did not read
+ * whole, which is the fail-open direction for callers that spend DELETEs on the answer. ABSENCE is
+ * still not unreadability: an action with no authorization block, or a block with no `provider_id`
+ * (the live `Airtable` shape in `fixtures/arcade-catalogue.json`), names no provider and is read as
+ * naming none, exactly as {@link authorizationRequirementOf} reads it.
+ */
+async function authProvidersNamedBy(
+ client: Arcade,
+ toolkit: string,
+): Promise {
+ const named = new Set();
+ let unreadable: AuthProvidersNamed["unreadable"] = null;
+ for await (const item of pagedItems({
+ pageSize: CATALOGUE_PAGE_SIZE,
+ maxPages: CATALOGUE_MAX_PAGES,
+ concurrency: CATALOGUE_CONCURRENCY,
+ page: (offset) =>
+ vendorCall(`the action listing for ${toolkit}`, () =>
+ client.tools.list({ toolkit, limit: CATALOGUE_PAGE_SIZE, offset }),
+ ),
+ malformed: `Arcade did not answer with an action list for ${toolkit}, so the auth provider its connections are held under could not be read.`,
+ unending: `Arcade's action list for ${toolkit} never ended after ${CATALOGUE_MAX_PAGES} pages, so the auth provider its connections are held under is not trusted.`,
+ incomplete: (seen, total) =>
+ `Arcade listed ${seen} of ${total} actions for ${toolkit}, so the auth provider its connections are held under could not be read in full.`,
+ unreadableTotal: (shown) =>
+ `Arcade reported the action list for ${toolkit} with a total_count of ${shown}, which is not a count, so whether the auth provider its connections are held under could be read in full is not known.`,
+ })) {
+ // Read to the END even after an unreadable action, so a truncated or malformed listing still
+ // throws its own sentence rather than being reported as one unreadable row.
+ const read = authorizationRequirementOf(item);
+ if (read.kind === "none") continue;
+ const action = textOf(record(item)?.qualified_name);
+ if (read.kind === "unreadable") {
+ unreadable ??= { action, field: read.field, shape: read.shape };
+ continue;
+ }
+ const providerId = read.authorization.provider_id;
+ if (providerId === undefined || providerId === null) continue;
+ const provider = textOf(providerId);
+ if (provider) named.add(provider);
+ else
+ unreadable ??= {
+ action,
+ field: "provider_id",
+ shape: shapeOf(providerId),
+ };
+ }
+ return { named, unreadable };
+}
+
+/**
+ * The Arcade client, in the shape `createComposioClient` returns. `new Arcade({ apiKey })` opens no
+ * socket and sends no request, so tests replace the SDK's PROTOTYPE methods before calling this.
+ *
+ * EVERY CALL TO THE VENDOR GOES THROUGH {@link vendorCall}, which is where a refusal becomes this
+ * deployment's own sentence and the vendor's body stops. `callTool` still reaches the body's
+ * `error.name` — a raw `APIError`'s `name` is the CLASS name through the prototype chain, never the
+ * body's, so without the normalization the authorize prompt would be unreachable — but it reaches
+ * it through {@link ArcadeFailureError}, not through a bare object thrown as if it were an error.
+ */
+export function createArcadeClient(apiKey: string): {
+ actions: ArcadeActions;
+ broker: ConnectedAppBroker;
+} {
+ const client = new Arcade({ apiKey });
+ const actions: ArcadeActions = {
+ // `total_count` IS NORMALIZED HERE, and this is the only place it can be. The transport
+ // (`./arcade`) must not import this file — that direction is a cycle — so it cannot reach
+ // {@link reportedTotalOf}, and what it receives has to already tell "the vendor did not say"
+ // apart from "the vendor said zero". Handing it the SDK's `OffsetPage` does not: that object's
+ // `total_count` is `body.total_count || 0`, so `listTools` read a `0` off an answer that
+ // carried no total and its `raw.length < total` cross-check could never fire. The FIELD IS
+ // OMITTED when the vendor omitted it, which is what the declared `total_count?: number` means.
+ listTools: async (q) => {
+ const answer = await vendorCall(
+ `the action listing for ${q.toolkit}`,
+ () =>
+ client.tools.list({
+ toolkit: q.toolkit,
+ limit: q.limit,
+ offset: q.offset,
+ }),
+ );
+ // Passed through unchecked: a malformed listing is the transport's sentence to write, and
+ // its own `Array.isArray` guard is what writes it. THE ITEMS ARE READ OFF THE RAW BODY for
+ // {@link sentItemsOf}'s reason — handing over the page's manufactured `[]` would leave that
+ // guard as dead as the totals the comment above describes, and a listing that answered no
+ // items at all would refresh an app's actions down to none.
+ const items = sentItemsOf(answer) as unknown[];
+ // AND A TOTAL THIS CANNOT READ REFUSES HERE TOO, rather than being dropped on the way out.
+ // The declared `total_count?: number` leaves the transport exactly two readings — a number
+ // to cross-check against, or "the vendor did not say" — so a value that is neither has
+ // nowhere honest to go: omitting it would switch off the transport's own
+ // `raw.length < total` check and commit a truncated refresh as a whole catalogue.
+ const total = reportedTotalOf(
+ answer,
+ (shown) =>
+ `Arcade reported the action listing for ${q.toolkit} with a total_count of ${shown}, which is not a count, so whether the listing is whole could not be checked. Nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+ return total === undefined ? { items } : { items, total_count: total };
+ },
+ execute: async (r) =>
+ vendorCall(`running ${r.tool_name}`, () =>
+ client.tools.execute({
+ tool_name: r.tool_name,
+ input: r.input,
+ user_id: r.user_id,
+ }),
+ ),
+ };
+ const broker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ listApps: () => listApps(client),
+ // 5.3: no auth config per app. Arcade's equivalent is an auth provider registered once in the
+ // dashboard for the whole project, so there is nothing to create or delete here.
+ ensureAuthConfig: async () => "not-needed",
+ deleteAuthConfig: async () => {},
+ // The field-entry pair REFUSES. Composio's field schemes are a person typing their own API key;
+ // `admin.secrets` is a deployment-wide secret an operator sets once for everybody.
+ connectionFields: () =>
+ Promise.reject(new BrokerRefusalError(FIELDS_REFUSAL)),
+ connectWithFields: () =>
+ Promise.reject(new BrokerRefusalError(FIELDS_REFUSAL)),
+ accountName: async ({ account, toolkit }) => {
+ // Filtered by the row's own provider, as Composio's `accountsFor(..., toolkit, ...)` is: a
+ // person connected to two apps must never be shown the OTHER app's name on this app's screen.
+ //
+ // AND BY THE ROW'S OWN `connection_status`, through {@link connectionIsLive}: this name is
+ // shown beside "Connected", so a name read off a row the vendor reports as expired or revoked
+ // says the connection is live in the one place somebody looks to check.
+ for (const row of await connectionsFor(
+ client,
+ account.vendorUserId,
+ toolkit,
+ )) {
+ if (!connectionIsLive(row)) continue;
+ const info = row.provider_user_info as
+ | { name?: unknown; login?: unknown }
+ | null
+ | undefined;
+ const name = textOf(info?.name) ?? textOf(info?.login);
+ if (name) return name;
+ }
+ return null;
+ },
+ // The mint. Stays a TOP-LEVEL member: the Composio connect flow calls it from routes.ts, so it
+ // cannot move behind the nullable `authorizations` sub-surface.
+ authorize: async ({ account, action }) => {
+ if (!action) {
+ throw new BrokerRefusalError(
+ "Arcade grants per action, so an authorization needs the action it is for and none was given.",
+ );
+ }
+ // NEVER SEND `next_uri`. THIS LOOKS LIKE A BUG AND IS A MEASUREMENT. The SDK declares the
+ // field (`ToolAuthorizeParams.next_uri`), but against the live API it refuses EVERY value and
+ // the mint answers `500 "error starting authorization challenge: invalid next URI"`: an app
+ // page, the verifier's own origin, `cloud.arcade.dev` and a localhost address alike. Do not
+ // "fix" this by passing a return address.
+ //
+ // `returnUrl` IS ACCEPTED AND IGNORED, for the same reason: the landing page comes from the
+ // attempt row (spec D18), not from a parameter this vendor will take.
+ //
+ // THE REFUSAL IS MEDIATED, AND ON THIS CALL THAT IS NOT A STYLE POINT: the body an
+ // authorization answers with is the one that carries a bearer `url`, and the SDK's `APIError`
+ // puts the body it could not read a `message` off into its own `message`. See
+ // {@link vendorCall}; the route's definite-or-uncertain decision reads `status` through
+ // `normalizeArcadeError` and is unaffected.
+ // THROUGH {@link requireBody}: a mint that answered an empty body, or a body of literal
+ // `null`, has `answer === null` wearing `AuthorizationResponse`, and the `status` read below
+ // would raise a bare `TypeError` rather than the sentence this route shows somebody.
+ const answer = requireBody(
+ `the authorization for ${action}`,
+ await vendorCall(`the authorization for ${action}`, () =>
+ client.tools.authorize({
+ tool_name: action,
+ user_id: account.vendorUserId,
+ }),
+ ),
+ );
+ // WHAT THE VENDOR SAID ABOUT THIS AUTHORIZATION, READ BEFORE ITS URL IS READ AS A NEXT STEP.
+ // `tools.authorize` does not only answer "go to this screen": it answers `completed` for an
+ // account that already holds the grant, `failed` for one the vendor could not begin — and it
+ // may answer either WITH a `url` beside it, so the absent-url refusal below is not this guard
+ // in disguise. Unread, both were handed back as a live consent link: somebody already
+ // connected is sent round a screen they finished, and a failed authorization is dressed up
+ // as a working one, with an `ar_…` recorded against the attempt that can never complete.
+ //
+ // AN ALLOWLIST OF THE SPELLINGS UNDER WHICH CONSENT IS GENUINELY THE NEXT STEP, for the
+ // reason {@link LIVE_CONNECTION_STATUS} gives: the dead vocabulary cannot be enumerated from
+ // here. The SDK's declared union is a convenience, not a promise about the wire — an engine
+ // answering `revoked` or `expired` type-checks as nothing and a denylist missing that
+ // spelling goes on sending a browser to it, which is the defect itself.
+ //
+ // SILENCE IS NOT A NO, as it is not in {@link connectionIsLive}: `status` is OPTIONAL on the
+ // wire, so an ABSENT field (missing, `undefined`, `null`) leaves the url and the id as the
+ // whole of the evidence and they stand. A status that is PRESENT AND UNREADABLE — `{}`,
+ // `false`, a number, a blank string — is not silence: the vendor said something about this
+ // authorization and this deployment cannot tell what, so the bearer url beside it is NOT
+ // handed on as a live redirect. {@link vendorStatusOf} is the one reading of both, and it
+ // FOLDS AND TRIMS the text for this file's standing reason: a case the vendor changes must not
+ // change what somebody is told about their own account.
+ //
+ // A `BrokerRefusalError` AND NOT A BARE `Error`, which is the difference between being told
+ // and being locked out. The mint route keys its three outcomes on what was thrown: an
+ // authored refusal is DEFINITE, so the attempt is discarded and the actor may press Authorize
+ // again at once, with this sentence shown; a bare `Error` normalizes to `null` and is read as
+ // "the mint may be live", leaving the attempt standing for its fifteen-minute TTL. Nothing
+ // IS live here — the vendor answered, and said this is not a consent to walk through.
+ const read = vendorStatusOf(answer.status);
+ if (read.kind === "unreadable") {
+ throw new BrokerRefusalError(
+ `Arcade answered this account's authorization for ${action} with ${read.shape} where its status belongs, so whether a consent screen is the next step could not be read and nobody was sent anywhere. Press Authorize again to start a fresh one.`,
+ );
+ }
+ const status = read.kind === "text" ? read.text : undefined;
+ if (status !== undefined && !MINT_AWAITS_CONSENT_STATUSES.has(status)) {
+ throw new BrokerRefusalError(
+ status === "completed"
+ ? `Arcade answered that this account's authorization for ${action} is already completed, so nobody was sent to a consent screen. Reload this page: the grant it is reporting is already in place.`
+ : `Arcade answered that this account's authorization for ${action} is "${status}" rather than one waiting for consent, so nobody was sent anywhere and nothing was connected. Press Authorize again to start a fresh one.`,
+ );
+ }
+ // The url is a bearer capability: it goes back to the caller and nowhere else.
+ const url = answer.url;
+ if (typeof url !== "string" || url === "") {
+ throw new Error(
+ "Arcade answered an authorization with no url to visit.",
+ );
+ }
+ // The mint's own `ar_…` id: spec D24 binds it to the callback's `auth_id`. A mint that
+ // answered none is not a success, and the refusal is here so that a null can never be
+ // minted from an Arcade answer — `authorizationId` is non-null for every Arcade mint.
+ const authorizationId = answer.id;
+ if (typeof authorizationId !== "string" || authorizationId === "") {
+ throw new Error("Arcade answered an authorization with no id.");
+ }
+ return { redirectUrl: url, authorizationId };
+ },
+ // 6.2: the connection-lifecycle trio.
+ // A LIVE ROW AND NOT MERELY A ROW. The seam's word is "currently attached to this app at the
+ // vendor", and a row Arcade reports as expired, revoked, pending or failed is not that; see
+ // {@link connectionIsLive}, which also says why a row with NO status still counts.
+ isConnected: async ({ account, toolkit }) =>
+ (await connectionsFor(client, account.vendorUserId, toolkit)).some(
+ connectionIsLive,
+ ),
+ // Answers what was REQUESTED: true when something was found and every delete was accepted,
+ // false when there was nothing. Every delete is attempted, then ANY failure throws: a partial
+ // ask must not read as `false` ("nothing there") nor as a clean `true`.
+ //
+ // AND `false` IS ONLY EVER SAID OFF A LISTING READ WHOLE, which is {@link connectionsFor}'s
+ // promise rather than this one's: an unreadable, truncated or UNATTRIBUTABLE listing throws out
+ // of it before this line, so `rows.length === 0` here means the vendor said so. `false` with no
+ // delete spent is the live defect's exact signature — the caller deletes the local row on it —
+ // and `arcade-revoke-invariant.test.ts` asserts the outcome rather than any one mechanism.
+ //
+ // ON EVERY ROW, LIVE OR NOT, WHICH IS WHY THIS DOES NOT ASK {@link connectionIsLive}. A dead row
+ // is still a row at the vendor: skipping it would leave it standing while this answered `false`
+ // ("there was nothing to withdraw"), and a reconnect would then be made beside debris nobody can
+ // see. Deleting a connection the vendor already considers finished costs one call and is the
+ // only reading under which Disconnect means what it says.
+ revoke: async ({ account, toolkit }) => {
+ const rows = await connectionsFor(client, account.vendorUserId, toolkit);
+ if (rows.length === 0) return false;
+ const outcomes = await Promise.allSettled(
+ rows.map((row) => deleteConnection(client, row.connection_id)),
+ );
+ const failed = outcomes.filter((o) => o.status === "rejected");
+ if (failed.length > 0) {
+ throw new Error(
+ `Arcade revoke failed for ${failed.length} of ${rows.length} connections.`,
+ { cause: (failed[0] as PromiseRejectedResult).reason },
+ );
+ }
+ return true;
+ },
+ revokeAccount: (accountId) => deleteConnection(client, accountId),
+ // 6.3: the read-only status pair. Both are DECLARED here because the seam requires them —
+ // `grantsFor` is a member every broker answers, and `authorizations` is the sub-surface Arcade
+ // alone runs, so a null here would say Arcade mints nothing to poll, which is false. The
+ // bodies are 6.3's. `status` must narrow to `{ status, userId, scopes }` and nothing else: the
+ // raw `auth.status` response carries `context.token`, a live provider access token, plus the
+ // person's full profile.
+ grantsFor: ({ account, toolkit }) =>
+ grantsFor(client, account.vendorUserId, toolkit),
+ // Read off the listing's own `requirements.authorization.provider_id`, never derived from the
+ // toolkit name and never falling back to it. See `authProviderOf` above.
+ authProviderOf: (toolkit) => authProviderOf(client, toolkit),
+ authorizations: {
+ /**
+ * Where one minted authorization has got to — THREE FIELDS READ OUT AND THE RESPONSE OBJECT
+ * DROPPED HERE, inside the adapter, so nothing outside this file ever holds it.
+ *
+ * THIS DELIBERATELY INVERTS THIS CODEBASE'S CARRY-THE-VENDOR-ERROR-AS-`cause` CONVENTION.
+ * That convention is right everywhere else: a vendor's own words are what a maintainer needs
+ * when a call fails. It is wrong here, because `auth.status` answers with `context.token` — a
+ * LIVE, WORKING provider access token for the person who just consented — and their full
+ * `user_info` profile beside it, while `url` is a bearer capability of its own. A status
+ * response LOOKS LIKE METADATA, which is exactly how the throwaway harness that proved this
+ * flow came to write the token to a file on its first run.
+ *
+ * So the protection is the RETURN TYPE and not a comment asking the next person to remember:
+ * what is not returned cannot be logged, attached as a `cause`, or written into an audit
+ * payload by code that never sees it. Do not widen this, and do not add `url`.
+ */
+ status: async (id, options) => {
+ // THE REFUSAL IS MEDIATED TOO, for the same reason the RESPONSE is: a status this vendor
+ // would not answer is answered ABOUT a flow whose body carries the token and the url.
+ //
+ // AND THE CALLER'S DEADLINE REACHES THE WIRE: an abandoned poll closes its socket rather
+ // than leaving this deployment holding one open against a vendor nobody is waiting on.
+ //
+ // AND THROUGH {@link requireBody}, for `authorize`'s reason: an empty body arrives here
+ // as `null` wearing the declared response type, and the three field reads below would
+ // raise a bare `TypeError` instead of saying what happened.
+ const answer = requireBody(
+ "an authorization status read",
+ await vendorCall("an authorization status read", () =>
+ client.auth.status({ id }, { signal: options?.signal }),
+ ),
+ );
+ const scopes = answer.scopes;
+ // THE ONE STATUS READING ({@link vendorStatusOf}): folded and trimmed, and an absent or
+ // unreadable status is `"unknown"`, which the return leg refuses as not finished.
+ const settled = vendorStatusOf(answer.status);
+ return {
+ status: settled.kind === "text" ? settled.text : "unknown",
+ userId: textOf(answer.user_id) ?? null,
+ scopes: Array.isArray(scopes)
+ ? scopes.filter((s): s is string => typeof s === "string")
+ : [],
+ };
+ },
+ // `handle` is opaque to the seam; `flow_id` is Arcade's word for it and stops in this file.
+ // A `user_mismatch` answer is an ANSWER, so it surfaces as `{ mismatch: true }`; any other
+ // failure is an outage or a bug and is rethrown as the sentence {@link vendorCall} wrote for
+ // it — `user_mismatch` is still read, because the body's `error.name` survives the mediation
+ // and `normalizeArcadeError` answers off the carried failure.
+ confirmReturn: async ({ handle, vendorUserId }, options) => {
+ let answer: { auth_id?: unknown };
+ try {
+ // THROUGH {@link requireBody}, for `authorize`'s reason. The refusal it writes is
+ // raised INSIDE the try, which is deliberate and harmless: `normalizeArcadeError`
+ // answers `null` for it, so it is not read as `user_mismatch` and is rethrown as
+ // itself by the `throw error` below — an unreadable body is an outage, which is the
+ // same judgement the `auth_id` check further down already states.
+ answer = requireBody(
+ "the return-leg confirmation",
+ await vendorCall("the return-leg confirmation", () =>
+ client.auth.confirmUser(
+ {
+ flow_id: handle,
+ user_id: vendorUserId,
+ },
+ { signal: options?.signal },
+ ),
+ ),
+ );
+ } catch (error) {
+ if (normalizeArcadeError(error)?.name === "user_mismatch") {
+ return { mismatch: true };
+ }
+ throw error;
+ }
+ // THE WHOLE SUBJECT OF THE BINDING, CHECKED HERE AS THE MINT CHECKS ITS OWN — see
+ // `authorize` above. `ConfirmUserResponse.auth_id` is DECLARED required by the SDK's types
+ // and is VALIDATED by nothing in it, so an answer that omits the field arrives as
+ // `undefined` wearing the type `string`, and `{ authorizationId: undefined }` would be a
+ // confirmation made against nothing that the seam's own type says is an id.
+ //
+ // AND IT THROWS RATHER THAN ANSWERING `{ mismatch: true }`, which is the contract routes.ts
+ // states at its binding: where the vendor's real shape differs from what was assumed, this
+ // does NOT answer a clean refusal — the return leg surfaces an outage, which is honest,
+ // because an id that could not be read is not evidence that the handle belongs to somebody
+ // else. A refusal reading "not your authorization" would blame the person for an answer
+ // their vendor failed to make.
+ const authorizationId = answer.auth_id;
+ if (typeof authorizationId !== "string" || authorizationId === "") {
+ throw new Error(
+ "Arcade confirmed a return with no authorization id.",
+ );
+ }
+ return { authorizationId };
+ },
+ },
+ };
+ return { actions, broker };
+}
diff --git a/server/src/plugins/arcade.ts b/server/src/plugins/arcade.ts
new file mode 100644
index 000000000..14705855a
--- /dev/null
+++ b/server/src/plugins/arcade.ts
@@ -0,0 +1,690 @@
+/**
+ * The Arcade transport's vendor-free half. Imports no vendor package and nothing from
+ * `arcade-adapter.ts`: the adapter imports this module, never the reverse (see `./broker`, which
+ * records that the other direction is a cycle).
+ */
+
+import { cutAtCodeUnits } from "../channels/text";
+import { flagOf } from "./broker";
+import { type ListedTool, MAX_RESULT_CHARS, type McpCallResult } from "./mcp";
+
+/**
+ * Whether listing tools needs somebody's credential. It does not: the listing uses the deployment's
+ * own Arcade key, and a listing is not a person's.
+ */
+export const listNeedsCredential = false;
+
+const TOOLKIT_NAME = /^[A-Za-z0-9_-]+$/;
+
+/**
+ * Which toolkit this connection is about, from `arcade://Toolkit`.
+ *
+ * CASE IS PRESERVED AND NOTHING IS NORMALIZED. Arcade's toolkit and action names are case-sensitive,
+ * and action names are dotted (`Apollo.EnrichOrganization`: the dot separates toolkit from action,
+ * so a toolkit name never contains one and `TOOLKIT_NAME` rightly rejects it), where Composio's
+ * slugs are neither, so a lowercased toolkit reaches nothing:
+ * `arcade://Gmail` and `arcade://gmail` are distinct apps.
+ *
+ * ONE TOOLKIT OR NOTHING. This answer is the name the brokered gate looks a person's row up by, so a
+ * url read loosely is one app's connection satisfying a call against another.
+ * `arcade://Gmail/messages` must not answer `"Gmail/messages"`: a path segment is not an app.
+ *
+ * TRIMMED BEFORE THE SLASHES COME OFF, as `composio.ts` does: the other order leaves a space as the
+ * last character, so the slash is not at the end and nothing matches.
+ */
+export function toolkitOf(url: string): string | null {
+ const prefix = "arcade://";
+ if (!url.startsWith(prefix)) return null;
+ const name = url.slice(prefix.length).trim().replace(/\/+$/, "");
+ return TOOLKIT_NAME.test(name) ? name : null;
+}
+
+/**
+ * What a tool does, from `metadata.behavior`. DESTRUCTIVE IS READ FIRST, and the order is the
+ * decision: a reading that checked `read_only` first would record a both-flagged tool as a READ,
+ * the one direction nothing downstream recovers from, since nothing ever asks again.
+ *
+ * ABSENT METADATA, OR A FLAG THAT `flagOf` ANSWERS NULL (a vendor publishing a string where it
+ * documents a boolean), IS A DESTRUCTIVE WRITE, which is also what a listing that would not say
+ * already records in `./mcp`. It fails safe by putting an approval in front of a read, which is
+ * annoying and correct; the alternative lets a deletion through unannounced.
+ *
+ * AN EMPTY BEHAVIOUR OBJECT IS A VENDOR THAT SAID NOTHING, AND IS READ EXACTLY AS AN ABSENT ONE.
+ * `{}` — or a block carrying only `idempotent` and the like — states NEITHER flag, so the two
+ * `flagOf(…, false)` defaults below would otherwise manufacture "not destructive, not read-only"
+ * out of nothing and answer the plain-write verdict: a container Arcade opened and put nothing in,
+ * read as Arcade declaring the action safe to run unannounced. The absence of a stated restriction
+ * is not the absence of a restriction, and an EMPTY container cannot be allowed to be worth more
+ * than NO container two lines above it.
+ *
+ * NOT THE SAME CALL AS `arcade-adapter.ts`'s `connectionIsLive`, WHERE SILENCE IS DELIBERATELY
+ * PERMISSIVE AND SAYS SO AT LENGTH. There
+ * the silent field is `connection_status`, optional on the wire, and a pessimistic reading
+ * un-connects every real connection against any engine that omits it — the cost of failing closed
+ * falls on people who did nothing wrong. Here the cost of failing closed is one approval prompt in
+ * front of an action that may turn out to be a read; the cost of failing open is a deletion that
+ * nobody was asked about. The flags' per-field defaults stand once EITHER flag is stated, which is
+ * the vendor describing this action rather than saying nothing at all.
+ */
+export function classify(metadata: unknown): {
+ effect: "read" | "write";
+ destructive: boolean;
+} {
+ const closed = { effect: "write", destructive: true } as const;
+ if (typeof metadata !== "object" || metadata === null) return closed;
+ const behavior = (metadata as { behavior?: unknown }).behavior;
+ if (typeof behavior !== "object" || behavior === null) return closed;
+ const flags = behavior as { destructive?: unknown; read_only?: unknown };
+ const stated = (value: unknown) => value !== undefined && value !== null;
+ if (!stated(flags.destructive) && !stated(flags.read_only)) return closed;
+ const destructive = flagOf(flags.destructive, false);
+ const readOnly = flagOf(flags.read_only, false);
+ if (destructive === null || readOnly === null) return closed;
+ if (destructive) return { effect: "write", destructive: true };
+ if (readOnly) return { effect: "read", destructive: false };
+ return { effect: "write", destructive: false };
+}
+
+/**
+ * What this transport needs of Arcade's client, and nothing more: `./arcade-adapter` maps the
+ * vendor's SDK onto it, so this file imports no vendor package. Mirrors `ComposioActions`.
+ *
+ * `listTools` carries no `user_id` by construction: the query type has no such field.
+ */
+export type ArcadeActions = {
+ listTools(q: {
+ toolkit: string;
+ limit: number;
+ offset: number;
+ }): Promise<{ items: unknown[]; total_count?: number }>;
+ execute(r: {
+ tool_name: string;
+ input: Record;
+ user_id: string;
+ }): Promise<{
+ success?: boolean;
+ output?: {
+ value?: unknown;
+ error?: { message?: string };
+ authorization?: unknown;
+ };
+ }>;
+};
+
+let installed: ArcadeActions | null = null;
+
+/** Installs (or, with `null`, removes) the Arcade client. Null is also the unconfigured state. */
+export function useArcadeClient(client: ArcadeActions | null): void {
+ installed = client;
+}
+
+export type ArcadeListedTool = ListedTool & { scopes: string[] };
+
+const PAGE_SIZE = 100;
+/** A runaway guard, not a bound: 1000 pages of 100 is far past any one toolkit. */
+const MAX_PAGES = 1000;
+
+function record(value: unknown): Record | null {
+ return typeof value === "object" && value !== null && !Array.isArray(value)
+ ? (value as Record)
+ : null;
+}
+
+/**
+ * WHAT ARRIVED WHERE A SHAPE BELONGED, named without quoting it.
+ *
+ * A listing row carries whatever the vendor chose to put on it, and a refusal from here is read off
+ * an admin page, so the SHAPE is the part that is both safe to say and the part that helps —
+ * `sent` in `composio-adapter.ts` says this at length and this is the same judgement, kept local
+ * because that file is the other vendor's door and neither imports the other.
+ */
+export function shapeOf(value: unknown): string {
+ if (Array.isArray(value)) return "a list";
+ // `typeof null` IS `"object"`, so the bare template would write "a object" — the wrong grammar
+ // and, worse, the wrong fact: a hole is not a shape this deployment read and could not use, it
+ // is the absence of one. The callers that pass a present-but-unreadable field guard both of
+ // these before asking; `connectionsFor`'s unreadable ROW is the one that can carry a `null`.
+ if (value === null || value === undefined) return "nothing";
+ if (typeof value === "string" && value.trim() === "") return "a blank string";
+ return `a ${typeof value}`;
+}
+
+/*
+ * ---------------------------------------------------------------------------------------------
+ * THE ONE READING OF A VENDOR AUTHORIZATION FIELD — absent, unreadable, or read.
+ *
+ * Every Arcade field this deployment draws an AUTHORIZATION conclusion from is read through one of
+ * the three functions below, and all three apply the same rule, stated once here:
+ *
+ * ABSENT — the key missing, `undefined`, or `null` — is the vendor saying nothing, and answers the
+ * field's documented default. `null` IS ABSENCE ON EVERY FIELD, NOT ON SOME: the SDK declares
+ * these fields optional, Arcade sends `metadata: null` and `requirements: null` for "none"
+ * (`AirtableApi`, `Apollo` in `fixtures/arcade-catalogue.json`), and a serializer that writes an
+ * unset optional as `null` rather than dropping the key is saying what a missing key says. A rule
+ * under which `null` meant "absent" on one field and "a value I cannot read" on the next would
+ * make the same vendor answer mean opposite things one line apart.
+ *
+ * UNREADABLE — present, but not the field's shape — is NEITHER default. Each caller decides what
+ * an unreadable field costs it, and every caller fails CLOSED: the catalogue and the transport
+ * listing refuse, the grants screen says `unknown`, a connection is not live, a mint is refused.
+ * A BLANK STRING IS UNREADABLE on a field whose value is a decision. It is present (so it is not
+ * absence) and carries no value (so it is not text); `authProvidersNamedBy` already refuses a
+ * blank `provider_id` for exactly this reason. `textOf` in `arcade-adapter.ts` reads a blank
+ * LABEL as absence, which is right for a display name and is not the question asked here.
+ *
+ * READ — the value in this deployment's vocabulary: a status FOLDED AND TRIMMED (a case or a
+ * space the vendor changes must not change an answer about somebody's account), a scope list
+ * whose every element is a scope.
+ *
+ * WHY ONE READING. `authorizationRequirementOf` (C2) answered this tri-state for the authorization
+ * block, and every OTHER reader of a field inside or beside it picked its own default for what it
+ * could not read — the permissive one, each time: a missing `token_status` fell back to the
+ * PROVIDER's `status: "active"` and drew every action as held; an unreadable `oauth2`/`scopes`
+ * became `[]`, which the gate reads as "the connection itself is the grant"; a non-text
+ * `connection_status` read as live; a non-text mint `status` handed a bearer url on as a live
+ * redirect. One reading means a new reader cannot pick a sixth default by accident.
+ * ---------------------------------------------------------------------------------------------
+ */
+
+/**
+ * What one listed action's `requirements.authorization` SAYS, read once for every caller that asks.
+ *
+ * THE ONE READING OF THIS FIELD'S ABSENCE. Two readers once reached opposite answers on the same
+ * row: `requiresAuthorization` read a missing block as "needs nothing" and filed the app as
+ * `no-auth`, while `grantStateOf` read the same silence as "could not be read" and drew every
+ * action of that app as a broker outage on the grants screen, forever. The vendor's own listing
+ * settles which is right — `Apollo` publishes NO authorization block and a deployment secret
+ * instead, verbatim in `fixtures/arcade-catalogue.json` — so `null` and an absent field are
+ * `none`, here and therefore everywhere.
+ *
+ * A PRESENT FIELD THIS DEPLOYMENT CANNOT READ IS NEITHER ANSWER. It is `unreadable`, with WHICH
+ * field and the shape that arrived there, and each caller decides what an unreadable row costs it:
+ * the catalogue refuses, the grants screen says `unknown`.
+ *
+ * HERE, IN THE TRANSPORT, AND NOT IN THE ADAPTER, because the transport's own listing reads the same
+ * block for the scopes the gate checks, and the adapter imports this module, never the reverse.
+ */
+export type AuthorizationRequirement =
+ | { kind: "none" }
+ | {
+ kind: "unreadable";
+ field: "requirements" | "authorization";
+ shape: string;
+ }
+ | { kind: "required"; authorization: Record };
+
+export function authorizationRequirementOf(
+ item: unknown,
+): AuthorizationRequirement {
+ const requirements = record(item)?.requirements;
+ if (requirements === undefined || requirements === null) {
+ return { kind: "none" };
+ }
+ if (typeof requirements !== "object" || Array.isArray(requirements)) {
+ return {
+ kind: "unreadable",
+ field: "requirements",
+ shape: shapeOf(requirements),
+ };
+ }
+ const authorization = (requirements as Record).authorization;
+ if (authorization === undefined || authorization === null) {
+ return { kind: "none" };
+ }
+ if (typeof authorization !== "object" || Array.isArray(authorization)) {
+ return {
+ kind: "unreadable",
+ field: "authorization",
+ shape: shapeOf(authorization),
+ };
+ }
+ return {
+ kind: "required",
+ authorization: authorization as Record,
+ };
+}
+
+/**
+ * The OAuth scopes one listed action asks for — `requirements.authorization.oauth2.scopes` — read
+ * by {@link authorizationRequirementOf}'s rule, one level further in.
+ *
+ * `[]` IS A CLAIM, NOT A SHRUG. An action whose scopes are `[]` is one whose connection IS the
+ * grant (Github's 43 actions publish exactly that), and the gate, the catalogue's counts and the
+ * grants screen all read it so. So `[]` is answered only when the vendor SAID it: no authorization
+ * block, an `oauth2` absent or `{}`, a `scopes` absent or `[]`. An `oauth2` or a `scopes` in any
+ * other shape is `unreadable`, and so is a list holding anything but scope text: `["repo", 5]`
+ * read as `["repo"]` would be a narrower grant than the one the vendor described, which is the same
+ * fail-open answer one element at a time.
+ */
+export type ScopesRequirement =
+ | { kind: "read"; scopes: string[] }
+ | {
+ kind: "unreadable";
+ field: "requirements" | "authorization" | "oauth2" | "scopes";
+ shape: string;
+ };
+
+export function requiredScopesOf(item: unknown): ScopesRequirement {
+ const read = authorizationRequirementOf(item);
+ if (read.kind === "none") return { kind: "read", scopes: [] };
+ if (read.kind === "unreadable") return read;
+ const oauth2 = read.authorization.oauth2;
+ if (oauth2 === undefined || oauth2 === null) {
+ return { kind: "read", scopes: [] };
+ }
+ if (typeof oauth2 !== "object" || Array.isArray(oauth2)) {
+ return { kind: "unreadable", field: "oauth2", shape: shapeOf(oauth2) };
+ }
+ const scopes = (oauth2 as Record).scopes;
+ if (scopes === undefined || scopes === null) {
+ return { kind: "read", scopes: [] };
+ }
+ if (!Array.isArray(scopes)) {
+ return { kind: "unreadable", field: "scopes", shape: shapeOf(scopes) };
+ }
+ const odd = scopes.findIndex(
+ (scope) => typeof scope !== "string" || scope.trim() === "",
+ );
+ if (odd !== -1) {
+ return {
+ kind: "unreadable",
+ field: "scopes",
+ shape: `a list holding ${shapeOf(scopes[odd])}`,
+ };
+ }
+ return { kind: "read", scopes: scopes as string[] };
+}
+
+/**
+ * One vendor STATUS field — `token_status`, `connection_status`, a mint's or a flow's `status` —
+ * read by the rule above: absent, unreadable, or its text FOLDED AND TRIMMED.
+ *
+ * NOT `textOf`, deliberately: `textOf` answers a blank string as absence, which is right for a
+ * label and wrong for a decision, and it does not fold. Each status reader once spelled its own
+ * `textOf(x)?.trim().toLowerCase()` (or forgot to), and the one that forgot answered a vendor-side
+ * change of case differently from the ones that did.
+ */
+export type VendorStatus =
+ | { kind: "absent" }
+ | { kind: "unreadable"; shape: string }
+ | { kind: "text"; text: string };
+
+export function vendorStatusOf(value: unknown): VendorStatus {
+ if (value === undefined || value === null) return { kind: "absent" };
+ if (typeof value !== "string" || value.trim() === "") {
+ return { kind: "unreadable", shape: shapeOf(value) };
+ }
+ return { kind: "text", text: value.trim().toLowerCase() };
+}
+
+/** Where on the action a {@link ScopesRequirement} could not be read, in words. */
+function scopesFieldOf(
+ field: Extract["field"],
+): string {
+ return field === "requirements"
+ ? "requirements"
+ : field === "authorization"
+ ? "authorization requirement"
+ : `authorization ${field}`;
+}
+
+/**
+ * The OAuth scopes an action needs, for the tool the gate checks.
+ *
+ * NEVER `requirements.met`. The two sit in the same object and behave oppositely: `met` is a
+ * per-person claim that answers TRUE FOR EVERYBODY when asked without a `user_id` (and this
+ * listing sends none), while `scopes` is a static property of the action that carries no identity.
+ * Reading `met` here would record every person as connected to everything, and nothing else would
+ * go red: the gate would pass, the call would be made, and the first sign would be somebody
+ * else's mailbox.
+ *
+ * AN UNREADABLE SCOPES BLOCK REFUSES THE LISTING; IT IS NEVER `[]`. `[]` on a listed tool is what
+ * the gate reads as "the connection is the grant", so answering it for a block this deployment
+ * could not read would let any connection run an action whose real scopes nobody read. See
+ * {@link requiredScopesOf}. The refusal names the shape, never the vendor's value.
+ */
+function scopesOf(tool: Record, toolkit: string): string[] {
+ const read = requiredScopesOf(tool);
+ if (read.kind === "read") return read.scopes;
+ throw new Error(
+ `Arcade listed one of ${toolkit}'s actions with ${read.shape} where its ${scopesFieldOf(read.field)} belongs, and that is what says which permissions it needs. A shape this deployment cannot read is not "no permissions", so nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+}
+
+function inputSchemaOf(input: unknown): Record {
+ const parameters = record(input)?.parameters;
+ const properties: Record = {};
+ const required: string[] = [];
+ for (const raw of Array.isArray(parameters) ? parameters : []) {
+ const parameter = record(raw);
+ if (!parameter || typeof parameter.name !== "string") continue;
+ const valType = record(parameter.value_schema)?.val_type;
+ properties[parameter.name] = {
+ type: valType === "json" ? "object" : (valType ?? "string"),
+ description:
+ typeof parameter.description === "string" ? parameter.description : "",
+ };
+ if (parameter.required === true) required.push(parameter.name);
+ }
+ return { type: "object", properties, required };
+}
+
+/**
+ * Every action of one toolkit, read to the end.
+ *
+ * NO `user_id` IS SENT. A listing is not somebody's: it is what this deployment offers everybody.
+ *
+ * PAGINATION STOPS ON A SHORT PAGE, never on `total_count`. That field is optional; an earlier draft
+ * fell back to `?? tools.length` and so exited after one page. It is used only as a cross-check.
+ */
+export async function listTools(connection: {
+ url: string;
+}): Promise {
+ const toolkit = toolkitOf(connection.url);
+ if (!toolkit) {
+ throw new Error(
+ `${connection.url} does not name an Arcade toolkit, so nothing was asked what it offers. The url has to be arcade:// followed by a toolkit name; correct it on the Plugins page.`,
+ );
+ }
+ if (!installed) {
+ throw new Error(
+ `Arcade is not configured for this deployment, so nothing could be asked what ${toolkit} offers. The actions already recorded for this app are kept rather than cleared.`,
+ );
+ }
+
+ const raw: unknown[] = [];
+ let total: number | undefined;
+ let exhausted = true;
+ for (let page = 0; page < MAX_PAGES; page++) {
+ let answer: { items: unknown[]; total_count?: number };
+ try {
+ answer = await installed.listTools({
+ toolkit,
+ limit: PAGE_SIZE,
+ offset: raw.length,
+ });
+ } catch (error) {
+ /*
+ * A SENTENCE, NOT THE VENDOR'S OBJECT, as `composio.ts`'s listing does. `refreshTools` puts
+ * `error.message` on the admin page and into a model's context, and an SDK `APIError` builds
+ * its message from the whole response body: an unsanitised vendor payload. The original is
+ * kept as `cause` for a log.
+ */
+ throw new Error(listingSentence(toolkit, error), { cause: error });
+ }
+ const items = (answer as { items?: unknown } | null)?.items;
+ if (!Array.isArray(items)) {
+ throw new Error(
+ `Arcade did not answer with an action list for ${toolkit}. Nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+ }
+ raw.push(...items);
+ // THE HIGHEST ANY PAGE REPORTED, NEVER THE LAST. An engine that truncates at 250 of 300 drops
+ // `total_count` from its short final page, and keeping the last value would erase the 300 page
+ // one reported and commit 250 actions as a complete listing. (`total_count` reaching here is
+ // already honest about absence: `createArcadeClient` omits the field when the vendor omitted
+ // it, because the SDK's page object coerces an absent total to `0`.)
+ if (typeof answer.total_count === "number") {
+ total =
+ total === undefined
+ ? answer.total_count
+ : Math.max(total, answer.total_count);
+ }
+ if (items.length < PAGE_SIZE) {
+ exhausted = false;
+ break;
+ }
+ }
+ if (exhausted) {
+ throw new Error(
+ `Arcade's listing for ${toolkit} never ended after ${MAX_PAGES} pages, so it is not trusted to be a list of this app's actions. Nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+ }
+ if (total !== undefined && raw.length < total) {
+ throw new Error(
+ `Arcade listed ${raw.length} of ${total} actions for ${toolkit}, so the listing is incomplete. Nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+ }
+
+ return raw.flatMap((item) => {
+ const tool = record(item);
+ if (!tool || typeof tool.qualified_name !== "string") return [];
+ const offerable = offerableName(tool.qualified_name, toolkit);
+ if (offerable === null) return [];
+ return [
+ {
+ name: offerable,
+ /*
+ * WHAT THE VENDOR IS CALLED BY, kept beside the name this deployment records rather than in
+ * place of it. `execute` sends this and the refusal sentences in this file quote it; the
+ * model is offered `name`. See {@link ListedTool.vendorName}.
+ */
+ vendorName: tool.qualified_name,
+ description:
+ typeof tool.description === "string" ? tool.description : "",
+ inputSchema: inputSchemaOf(tool.input),
+ ...classify(tool.metadata),
+ scopes: scopesOf(tool, toolkit),
+ },
+ ];
+ });
+}
+
+/**
+ * The part of an Arcade `qualified_name` this deployment records as the action's name, or null when
+ * the listing offers nothing that can be one.
+ *
+ * ARCADE QUALIFIES EVERY ACTION WITH ITS TOOLKIT — `Gmail.ListEmails` — and the toolkit is already
+ * the server id, so carrying the prefix would spell the app twice in every ref and every offered
+ * name. More to the point the dot is ILLEGAL in a model tool name (`^[a-zA-Z0-9_-]+$`), which is
+ * the defect this function exists to close: recorded whole, the qualified name went into
+ * `mcp__arcade-Gmail__Gmail.ListEmails` and the model API refused the entire request, every tool in
+ * it, on the first granted Arcade action.
+ *
+ * ONLY THE VENDOR'S OWN PREFIX IS REMOVED, and only when the remainder stands on its own. Anything
+ * else — a differently-qualified name, a second dot, a character no tool name may carry — is left
+ * for the guard in `storableTools` to refuse by name, because a listing this does not recognise is
+ * not something to repair by guessing. Dropped here rather than refused outright for the one case
+ * the vendor's own schema permits and this deployment can do nothing with: an empty remainder.
+ */
+function offerableName(qualifiedName: string, toolkit: string): string | null {
+ const prefix = `${toolkit}.`;
+ const bare = qualifiedName.toLowerCase().startsWith(prefix.toLowerCase())
+ ? qualifiedName.slice(prefix.length)
+ : qualifiedName;
+ return bare.trim().length === 0 ? null : bare;
+}
+
+/** What this deployment says when Arcade's listing could not be read. Never the vendor's words. */
+function listingSentence(toolkit: string, _error: unknown): string {
+ return `Arcade's action list for ${toolkit} could not be read, so nothing was refreshed and the actions already recorded for this app are kept.`;
+}
+
+/**
+ * A failure from Arcade, already normalized and cause-unwrapped by `./arcade-adapter`, so nothing
+ * here sees an SDK class. `name` is the RESPONSE BODY's `error.name`.
+ */
+export type ArcadeFailure = {
+ status: number | undefined;
+ name: string | undefined;
+ message: string | undefined;
+};
+
+/**
+ * A REFUSAL FROM ARCADE AS A REAL `Error`, CARRYING THIS DEPLOYMENT'S SENTENCE AND NOT THE VENDOR'S.
+ *
+ * TWO DEFECTS IN ONE CLASS, and both were measured rather than imagined.
+ *
+ * A BARE OBJECT IS NOT A REASON. `createArcadeClient`'s `execute` used to `throw` the normalized
+ * failure itself — a plain `{ status, name, message }`. `reasonWithoutStatement`, which is the one
+ * door every log line in `routes.ts` copies a reason through, answers `String(thrown)` for anything
+ * that is not an `Error`: the literal text `[object Object]`. So the single log line written at the
+ * moment a tool call failed carried no information whatsoever, and there was no stack beside it.
+ *
+ * AND THE VENDOR'S WORDS ARE DROPPED HERE, which is the half the type enforces. The SDK's `APIError`
+ * builds its own `message` as `${status} ${body.message ?? JSON.stringify(body)}`
+ * (`@arcadeai/arcadejs`, `core/error.js: makeMessage`), so an `APIError` allowed to escape the
+ * adapter puts the vendor's RESPONSE BODY into every reader that quotes a message — an admin page,
+ * a log line, a model's context, an audit row. On this vendor that body is not inert: the auth
+ * paths answer with an `authorization` whose `url` is a bearer capability and whose `context.token`
+ * is a live provider access token, and the adapter's `authorizations.status` already inverts this
+ * codebase's carry-the-vendor-error-as-`cause` convention for exactly that reason. This is the same
+ * refusal one layer out: the constructor keeps `status` and the body's `error.name` — the two
+ * MACHINE fields this deployment keys decisions on, in {@link isAuthorizationRequired} and in the
+ * route that decides whether a failed mint is definite — and drops the vendor's PROSE on the floor.
+ * Nothing that is not stored cannot be logged.
+ *
+ * `status` IS ALSO A TOP-LEVEL FIELD, because that is what an `APIError` presents and callers that
+ * read it off the thrown value go on working across this change.
+ */
+export class ArcadeFailureError extends Error {
+ readonly status: number | undefined;
+ readonly failure: ArcadeFailure;
+ constructor(sentence: string, failure: ArcadeFailure) {
+ super(sentence);
+ this.name = "ArcadeFailureError";
+ this.status = failure.status;
+ // THE ONE DOOR THE VENDOR'S PROSE DOES NOT COME THROUGH. Written here rather than at each
+ // construction site so that a later caller cannot pass it on by forgetting to.
+ this.failure = {
+ status: failure.status,
+ name: failure.name,
+ message: undefined,
+ };
+ }
+}
+
+/**
+ * KEYED ON THE BODY'S NAME, NEVER ON THE 403. A 403 is also what a revoked key, a disabled project
+ * and a rate limit look like; keyed on the status, this would tell somebody to authorize an action
+ * when the deployment's key is what is wrong.
+ */
+export function isAuthorizationRequired(failure: ArcadeFailure): boolean {
+ return failure.name === "tool_authorization_required";
+}
+
+/**
+ * The thrown value, READ AS A SHAPE rather than cast into one. `record` is a plain-object coercion
+ * that normalizes nothing and walks no `cause`, so a cast through it would let any object at all
+ * stand in for a normalized failure. Only a `name` string is required, since that is the one field
+ * `isAuthorizationRequired` keys on; the rest are read as-is or left undefined.
+ *
+ * {@link ArcadeFailureError} IS ASKED FIRST AND BY TYPE, because the shape reading cannot see it:
+ * an `Error`'s `name` is its CLASS name through the prototype chain, so a duck-typed read of one
+ * answers `"ArcadeFailureError"` for every refusal and the authorize prompt below would be
+ * unreachable — which is the same trap the adapter's own comment records for a raw `APIError`.
+ */
+function arcadeFailure(thrown: unknown): ArcadeFailure | null {
+ if (thrown instanceof ArcadeFailureError) return thrown.failure;
+ const raw = record(thrown);
+ if (!raw || typeof raw.name !== "string") return null;
+ return {
+ status: typeof raw.status === "number" ? raw.status : undefined,
+ name: raw.name,
+ message: typeof raw.message === "string" ? raw.message : undefined,
+ };
+}
+
+/**
+ * The cap every string this module puts in front of a model goes through, as `composio.ts`'s
+ * `cap()` does, with the same limit and the same marker.
+ *
+ * A tool result is read by a model AND copied into the audit row, so an unbounded one is somebody
+ * else's server deciding how much of our context window to spend and how large a durable record
+ * grows — and the text is attacker-influenceable, since it is whatever the mailbox, issue or
+ * document the action read happens to contain. Truncated visibly, never silently: `truncated` is
+ * the field that says it happened, and reporting `false` unconditionally made it a field that
+ * structurally could not.
+ *
+ * BOTH ANSWERS GO THROUGH IT. A refusal lands in a model's context exactly as a result does, and
+ * this file's refusals quote `toolName`, which is a `qualified_name` the vendor's listing supplied.
+ */
+function cap(text: string): { text: string; truncated: boolean } {
+ if (text.length <= MAX_RESULT_CHARS) return { text, truncated: false };
+ return {
+ text: `${cutAtCodeUnits(text, MAX_RESULT_CHARS)}\n\n[truncated]`,
+ truncated: true,
+ };
+}
+
+const failureResult = (text: string): McpCallResult => ({
+ ...cap(text),
+ isError: true,
+});
+
+/**
+ * Call one Arcade action as the connection's actor.
+ *
+ * THE ACTION IN THE REFUSAL COMES FROM THE CALL SITE, never from the error: `APIError` in
+ * `@arcadeai/arcadejs@2.4.1` carries `status`, `headers` and `error` and nothing naming the tool.
+ *
+ * NO URL IS EVER IN THE SENTENCE, ON ANY PATH. The `authorization.url` is a bearer capability:
+ * whoever opens it attaches an account to the identity the flow was minted for. It is never
+ * returned to a model, logged, put in an error body or written to an audit row. The refusal names
+ * the Settings page, where the link is minted by an authenticated request whose actor is known.
+ *
+ * THE SUCCESS PATH IS COVERED BY THAT SAME PROMISE, and this is where it is easiest to break. A 200
+ * is not a safe envelope: `ExecuteToolResponse.output.authorization` is the SAME
+ * `AuthorizationResponse` the 403 carries, with the url and a `context.token` that is a LIVE OAUTH
+ * ACCESS TOKEN for the end user's account. `output.value` IS THE ONLY PART OF THE ENVELOPE THAT MAY
+ * CROSS INTO `text`; the envelope is never stringified whole, because `text` is returned to a model
+ * and copied into the audit row. An answer carrying an `authorization` at all is the refusal, not a
+ * result: it means the call was not run as an authorized actor.
+ *
+ * NOTHING THE VENDOR SAID IS REPEATED either: the ordinary failure is this deployment's sentence,
+ * and `output.error` is read only as a BOOLEAN — its `message`, `developer_message` and
+ * `stacktrace` are the vendor's words and stay out of `text`.
+ */
+export async function callTool(
+ connection: { url: string; actorId?: string },
+ toolName: string,
+ args: Record,
+): Promise {
+ if (!installed) {
+ return failureResult(
+ `Arcade is not configured for this deployment, so ${toolName} was not run.`,
+ );
+ }
+ if (!connection.actorId) {
+ return failureResult(
+ `${toolName} was not run: no account was named to run it as.`,
+ );
+ }
+ try {
+ const answer = await installed.execute({
+ tool_name: toolName,
+ input: args,
+ user_id: connection.actorId,
+ });
+ if (answer.output?.authorization) {
+ return failureResult(
+ `${toolName} has not been authorized on this account yet. Authorize it in Settings → Connected accounts and ask again.`,
+ );
+ }
+ if (answer.success === false || answer.output?.error) {
+ return failureResult(`Arcade could not run ${toolName}.`);
+ }
+ const value = JSON.stringify(answer.output?.value ?? null, null, 2);
+ /*
+ * `JSON.stringify` ANSWERS `undefined` RATHER THAN THROWING for a value with no JSON form — a
+ * function, a symbol — and `output.value` is the vendor's field while the `unknown` saying
+ * anything may be there is ours. `??` does not catch it: a function is not nullish. Named here
+ * so the refusal is this deployment's sentence rather than a `text.length` TypeError raised
+ * inside {@link cap} and caught below as if Arcade had failed.
+ */
+ if (value === undefined) {
+ return failureResult(
+ `${toolName} ran, but its answer has no JSON form, so there is nothing to show.`,
+ );
+ }
+ return { ...cap(value), isError: false };
+ } catch (thrown) {
+ const failure = arcadeFailure(thrown);
+ if (failure && isAuthorizationRequired(failure)) {
+ return failureResult(
+ `${toolName} has not been authorized on this account yet. Authorize it in Settings → Connected accounts and ask again.`,
+ );
+ }
+ return failureResult(`Arcade could not run ${toolName}.`);
+ }
+}
diff --git a/server/src/plugins/broker.ts b/server/src/plugins/broker.ts
index 8595a1080..681f0812f 100644
--- a/server/src/plugins/broker.ts
+++ b/server/src/plugins/broker.ts
@@ -10,9 +10,24 @@
* schema. Everything below is a name for a shape, so the vendor's package stays confined to the
* adapter that implements {@link ConnectedAppBroker} — one file, replaceable, and the only place a
* version bump can reach. A module that named the vendor's types here would put their package on
- * the import graph of every test that touches enablement. `AccountRef`, below, is the one import
- * that crosses this file's boundary, and it is a type this deployment names, never the vendor's.
+ * the import graph of every test that touches enablement. `AccountRef`, `BrokerProvider` and
+ * `BROKERED_PROFILE`, below, are the only imports that cross this file's boundary, and all three are
+ * names this deployment owns, never the vendor's. Two are types; `BROKERED_PROFILE` is a value, and
+ * the comment on its import says what that costs.
*/
+// This import closes a runtime cycle: `./access` imports `schemeKind` from this file. It is safe
+// for two reasons, and the reasons are the rule. `access.ts` reads `schemeKind` only inside a
+// function body, and this file reads `BROKERED_PROFILE` only inside a refusal constructor — the
+// `BrokerUnconfiguredError` one and the `BrokerReturnUrlError` one — so whichever module evaluates
+// first, the other's half-initialised bindings are never touched while either is still evaluating.
+// Nothing may call across this cycle at module scope.
+// Hoisting a `BROKERED_PROFILE[provider]` destructure out of either constructor, or deriving a
+// top-level constant in `access.ts` from `schemeKind`, would read a lexical binding the other module
+// has not yet initialised: it throws under one evaluation order and not the other, so it can pass
+// every test and still fail at boot. The cycle is deliberate: passing `vendor` and `envVar` in
+// would scatter the vocabulary across every call site, which is what keeping `BROKERED_PROFILE` in
+// `access.ts` exists to prevent.
+import { BROKERED_PROFILE, type BrokerProvider } from "./access";
import type { AccountRef } from "./shared-accounts";
/**
@@ -317,6 +332,43 @@ export type BrokerApp = {
* made the choice this field exists to inform.
*/
actionCount: number;
+ /**
+ * How many of those actions the provider itself classified, or null where nobody measured.
+ *
+ * REQUIRED AND NULLABLE, WHICH IS THE WHOLE POINT OF THE FIELD. `0` means MEASURED, AND NOTHING
+ * WAS CLASSIFIED — every action of this app will be approved like a deletion, so the coverage
+ * warning fires. `null` means NOT MEASURED — this provider does not publish the fact, so the page
+ * says nothing. Those are opposite sentences.
+ *
+ * An OPTIONAL field makes them the same shape at the call site, and the natural `?? 0` collapses
+ * "unmeasured" into "measured none" — firing a coverage warning on a Composio deployment, a false
+ * alarm at exactly the moment somebody is deciding whether to trust an app. So every projection
+ * states the value, `./composio-adapter` writes an explicit `null`, and NOTHING WRITES `?? 0`.
+ */
+ classifiedCount: number | null;
+ /**
+ * How many DISTINCT NON-EMPTY scope sets the app's actions ask for, or null where nobody measured.
+ *
+ * The count of things that are separately approvable. Two actions asking for the same scopes are
+ * one permission; `[a,b]` and `[b,a]` are the same set and so are one too, which is why the sets
+ * are SORTED before they are joined.
+ *
+ * AN ACTION WITH NO SCOPES IS NOT ITS OWN PERMISSION — measured, not chosen. Github's 43 actions
+ * all carry an `oauth2` block with no scopes, and one read consent flipped 43 of 43 to
+ * `met: true`. So "no scopes" never meant "needs nothing"; it meant "the connection itself is the
+ * grant, and any consent covers this". Such an action is not separately approvable, so it joins
+ * no group and this count does not move for it. See {@link BrokerApp.scopedActionCount}.
+ */
+ permissionCount: number | null;
+ /**
+ * How many actions carry AT LEAST ONE scope, or null where nobody measured.
+ *
+ * IT EXISTS BECAUSE WITHOUT IT A MIXED APP LIES BY ARITHMETIC. A toolkit of two actions where one
+ * asks for scopes and one asks for none reports `permissionCount: 1` across `actionCount: 2`,
+ * which reads as ONE PERMISSION COVERING BOTH — while the second is in fact covered by any
+ * approval at all. Those are different facts, and this is the field that tells them apart.
+ */
+ scopedActionCount: number | null;
/** How somebody would connect to it. See {@link BrokerConnection}. */
connection: BrokerConnection;
};
@@ -327,6 +379,100 @@ export type BrokerApp = {
*/
export type AuthConfigOutcome = "created" | "standing" | "not-needed";
+/**
+ * What one account holds at one app, in THIS DEPLOYMENT'S OWN VOCABULARY rather than a vendor's row.
+ *
+ * `action` PLUS A STATE PLUS `scopes` IS OPENBOT'S SHAPE AND NOT ARCADE'S. A provider that grants
+ * per action answers one of these per action because that is what it knows; a provider that grants
+ * per app answers the same state once per action because that is what IT knows, and neither is
+ * reading the other's rows. The four states are what a caller can act on — run it, send them to a
+ * consent, tell an operator to set something up, say that nothing could be read — and `unknown`
+ * carries its own reason because a state nobody could read is a real answer and not a defensive one.
+ *
+ * A DEGENERATE ANSWER IS NOT AN ABSENT ONE, which is why {@link ConnectedAppBroker.grantsFor} is on
+ * the seam rather than behind a nullable sub-surface. Composio's auth config fixes the scope set
+ * when it is created, so every action in a toolkit shares one state and one scope list; N identical
+ * entries is the honest rendering of a toolkit-wide grant, not a stub standing in for a missing
+ * answer.
+ *
+ * `authorized` SAYS WHY THE ACTION IS USABLE, BECAUSE TWO DIFFERENT FACTS ANSWER IT. `held` is an
+ * authorization this account holds — a consent somebody gave, which a later consent screen is
+ * talking about. `not-required` is the vendor publishing no authorization for the action at all
+ * (Arcade's `Apollo`): it is usable, and no approval ever withheld it or will ever grant it. Both
+ * are `authorized` because both can be called; they are told apart because the consent copy's "approving this
+ * approves all N" counts only the first, and an action nobody has to approve is not something an
+ * approval covers. REQUIRED, not optional: an absent field would quietly read as one of the two.
+ */
+export type GrantState =
+ | {
+ action: string;
+ state: "authorized";
+ scopes: string[];
+ authorization: "held" | "not-required";
+ }
+ | { action: string; state: "needs-consent"; scopes: string[] }
+ | { action: string; state: "needs-operator"; missing: string[] }
+ | { action: string; state: "unknown"; reason: string };
+
+/**
+ * The CORRELATED, POLLABLE AUTHORIZATION FLOW one provider runs and the other does not.
+ *
+ * THE LINE THIS SUB-SURFACE FALLS ON IS "THIS BROKER RUNS A CORRELATED, POLLABLE AUTHORIZATION
+ * FLOW" — NOT "THIS BROKER CAN START A CONSENT", WHICH BOTH DO. {@link ConnectedAppBroker.authorize}
+ * therefore stays top-level and must not be tidied in here: its caller is the Composio connect flow
+ * in `routes.ts`, and moving it would either break that route or force Composio to declare this
+ * sub-surface, which would make the nullability mean nothing.
+ *
+ * ASKING THE OTHER PROVIDER FOR THE STATUS OF A MINT IS A CATEGORY ERROR RATHER THAN A HARD
+ * QUESTION. Composio mints no correlated authorization to poll, and its return leg is an OAuth
+ * callback with state handled in `oauth.ts` and never through the broker. A member that existed only
+ * to throw would be a provider branch wearing a type's clothes — which is the charge this seam
+ * already met once at `accountName`, two implementations of one member answering different
+ * questions. That charge does NOT apply to {@link ConnectedAppBroker.grantsFor}, which does not
+ * throw.
+ *
+ * `status` IS NAMED `status` AND NOT `authorizationStatus`: the sub-surface supplies the noun.
+ */
+export type AuthorizationFlow = {
+ /**
+ * Where one minted authorization has got to, by the id the mint answered with.
+ *
+ * NARROWED TO THESE THREE FIELDS AND NOTHING ELSE. A provider's own status reply carries the live
+ * access token it issued and the person's full profile beside it, and neither is something a seam
+ * this deployment reads in routes and stores should be able to hand on by accident.
+ *
+ * `signal` IS OPTIONAL ON THE SEAM AND HONOURED BY THE ADAPTER. The caller polls this on a leg
+ * where a browser is held open, and a poll COUNT bounds nothing while each call is unbounded —
+ * so the caller gives up on its own schedule and says so here, and the adapter hands the signal
+ * to the SDK's request options rather than leaving a socket open against a vendor nobody is
+ * waiting on any more. A stub that ignores it is behaving correctly: an abandoned call is still
+ * abandoned, the signal only makes the abandonment reach the wire.
+ */
+ status(
+ id: string,
+ options?: { signal?: AbortSignal },
+ ): Promise<{ status: string; userId: string | null; scopes: string[] }>;
+ /**
+ * Match somebody coming back from the provider's screen to the authorization they left through.
+ *
+ * `handle` IS AN OPAQUE STRING THIS SEAM DOES NOT INTERPRET. It is minted by the adapter, carried
+ * by the return leg and handed back here to be recognised; `flow_id` is Arcade's word for it and
+ * stops at the adapter. Nothing here parses it, compares it to a scheme or reads a provider out of
+ * it.
+ *
+ * `{ mismatch: true }` RATHER THAN A THROW, because a handle that belongs to somebody else is an
+ * ANSWER: it is exactly the case a return leg has to be able to refuse quietly, and a caller
+ * cannot tell it from an outage if both arrive as exceptions.
+ */
+ confirmReturn(
+ q: {
+ handle: string;
+ vendorUserId: string;
+ },
+ options?: { signal?: AbortSignal },
+ ): Promise<{ authorizationId: string } | { mismatch: true }>;
+};
+
/**
* What this deployment needs of Composio's broker, and nothing more.
*
@@ -338,6 +484,104 @@ export type AuthConfigOutcome = "created" | "standing" | "not-needed";
* dial.
*/
export type ConnectedAppBroker = {
+ /**
+ * Whether this provider's grant is held by THIS deployment, as opposed to by the vendor.
+ *
+ * A DECLARED PROPERTY, AND NEVER A `provider === "arcade"` COMPARISON. Every permission check
+ * that has to know where a grant lives reads this field off the broker it was handed; none of
+ * them asks which vendor it is talking to. `store.ts:1631` is why: a gate and its exemption
+ * keyed on different things there, and the exemption failed OPEN — a duplicate row let a call
+ * run for somebody who had connected nothing. A vendor-name comparison inside a permission check
+ * is that same shape, two answers to one question that nothing forces to agree, and adding a
+ * provider would mean finding every such comparison by hand. A property on the seam makes the
+ * answer one value per broker and makes a new broker declare it or fail to type-check.
+ *
+ * TRUE for Composio, whose connection rows and grants live in this deployment's own tables. That
+ * is the behaviour it always had, made explicit here rather than changed.
+ */
+ grantIsLocal: boolean;
+ /**
+ * The correlated authorization flow, or a DECLARED `null` where this broker runs none.
+ *
+ * THE FIELD IS REQUIRED AND THE `null` IS A STATEMENT RATHER THAN AN INFERRED ABSENCE. A broker
+ * literal that omits it fails to type-check, which is the whole of what this costs and the whole
+ * of what it buys. {@link ConnectedAppBroker.grantIsLocal} is the shape being copied and its
+ * docblock argues for it in its own words: an optional-shaped capability read with `?.` or
+ * `=== undefined` answers for a broker that never declared anything, which is how a gate and its
+ * exemption came to key on different things at `store.ts:1631` and the exemption failed OPEN —
+ * a call ran for somebody who had connected nothing. Nine stubs omitting `grantIsLocal` were the
+ * evidence; an optional member here would reopen that exact shape one field along.
+ *
+ * SO A ROUTE REACHES IT WITH `if (!broker.authorizations)` — A PRESENCE TEST ON A VALUE THE
+ * BROKER DECLARED, and never a vendor name. Which flow a provider runs is the provider's own
+ * answer, stated once on the object it hands over.
+ *
+ * WHAT IT IS AND IS NOT ABOUT: see {@link AuthorizationFlow}. The line is "this broker runs a
+ * correlated, pollable authorization flow", NOT "this broker can start a consent" — both do that,
+ * through {@link ConnectedAppBroker.authorize}, which stays top-level.
+ */
+ authorizations: AuthorizationFlow | null;
+ /**
+ * What this account holds at this app, action by action.
+ *
+ * THE NEUTRAL SEAM BOTH PROVIDERS ANSWER, and it is one question rather than two: what does this
+ * account hold at this app. A provider that grants per action answers from its own rows; a
+ * provider whose auth config fixes the scope set when it is created answers the same state once
+ * per action, carrying that config's scopes. The second answer is DEGENERATE AND DEGENERATE IS
+ * NOT ABSENT — it is what a toolkit-wide grant honestly looks like through this vocabulary, and
+ * an implementation that threw instead would be two implementations of one member answering
+ * different questions, which is the real bug this seam already found at `accountName`.
+ *
+ * {@link GrantState} IS THIS DEPLOYMENT'S VOCABULARY AND NOT A VENDOR'S ROW SHAPE. Nothing here
+ * is copied across from a provider's listing; see that type.
+ */
+ grantsFor(q: { toolkit: string; account: AccountRef }): Promise;
+ /**
+ * The VENDOR'S OWN AUTH PROVIDER a connection for this toolkit is held under, or null where the
+ * vendor does not say.
+ *
+ * IT IS NOT THE TOOLKIT NAME, AND THE COINCIDENCE IS NOT THE RULE. Arcade keys a connection on
+ * `(user_id, provider_id)` rather than on a toolkit: one Google provider serves `Gmail` and
+ * `GoogleCalendar` both. A provider equals the toolkit slug only where an operator named it that
+ * way — which `Github`, `Asana` and `Attio` all happen to do, and those are every toolkit in this
+ * tree's fixtures and stubs. So a derivation from the toolkit name, or a fallback to it where the
+ * vendor is silent, would pass every test here and be wrong about the deployment it was written
+ * for. `null` is the honest answer, and its callers leave such an app out rather than guess.
+ *
+ * WHAT THIS MEMBER ANSWERS WITH IS THE PROVIDER TYPE, AND `provider_id` NAMES THREE DIFFERENT
+ * THINGS ON THREE ARCADE OBJECTS. Measured against the live API:
+ *
+ * - on a TOOL LISTING, `requirements.authorization.provider_id` is the provider TYPE (`google`) —
+ * this is the one this member reads, and the one it returns;
+ * - on a USER-CONNECTION ROW, `provider_id` is the provider INSTANCE id, an operator's
+ * configuration OF a type (`google-openbot`, `arcade-google`, `cpk-poc`). A refusal envelope's
+ * `"provider_id": "cpk-poc"` is an instance too;
+ * - on an AUTH-PROVIDER CONFIG the two sit side by side — `id` is the INSTANCE and `provider_id`
+ * is the TYPE — which is the only bridge between the first two, because a connection row
+ * carries no vendor-type field at all: its `provider_type` is `"oauth2"`, the auth SCHEME.
+ *
+ * SO A CALLER MUST NOT COMPARE THIS ANSWER TO A CONNECTION ROW'S `provider_id`. `"google"` never
+ * equals `"google-openbot"`, so that comparison matched zero rows on every real deployment —
+ * Arcade's own defaults included, since those are `arcade-google` and `arcade-github` and never
+ * the bare type — and it read as "connected to nothing" rather than as an error. A caller wanting
+ * rows expands this type into the SET of configured instance ids of that type first; the Arcade
+ * adapter's `connectionsFor` is where that is done and why.
+ *
+ * NAMED `authProviderOf` AND NEVER `providerIdOf`. In this codebase `provider` means the BROKER —
+ * {@link BrokerProvider}, `brokered_connections.provider` — and that collision has already been
+ * paid for once, when an audit key `provider` was renamed `authProvider` because it meant the auth
+ * provider while a column fifty lines away meant the broker.
+ *
+ * TOP-LEVEL RATHER THAN ON {@link ConnectedAppBroker.authorizations}, because it is not a step in
+ * a flow and it is not a question only one provider can answer. Both answer it; see
+ * {@link ConnectedAppBroker.grantsFor} for the same line drawn one member along, and for why a
+ * DEGENERATE answer — a vendor whose connections are per toolkit, so a toolkit is its own auth
+ * provider and the shared set is always empty — is an answer rather than an absence.
+ *
+ * CASE IS CARRIED THROUGH UNTOUCHED. Whether two providers are the same provider is the caller's
+ * comparison to make, and Arcade's toolkit and action case is never normalised here.
+ */
+ authProviderOf(toolkit: string): Promise;
/** Every app the catalogue offers, which is what an administrator chooses from. */
listApps(): Promise;
/**
@@ -397,6 +641,22 @@ export type ConnectedAppBroker = {
* connection, so it is neither stored nor logged nor put in an audit row: it is handed to the
* browser that asked for it and then forgotten. A redirect url in a log is somebody else's
* mailbox for as long as it stays valid.
+ *
+ * IT ANSWERS THE MINT'S OWN ID BESIDE THE URL, AND THE ID IS REQUIRED AND NULLABLE, NEVER
+ * OPTIONAL. `authorizationId` is the handle the vendor gave this one mint, which is what a
+ * return leg is checked against (spec D24: the attempt's id must equal the callback's
+ * `auth_id`). An OMITTED field is an inferred absence, and the natural `?? something` at a call
+ * site turns "this broker mints no id" into a value — an empty string or a made-up id that a
+ * verifier would then compare against. A required nullable field makes the `null` a DECLARED
+ * STATEMENT, the same shape {@link ConnectedAppBroker.grantIsLocal} and
+ * {@link ConnectedAppBroker.authorizations} have in this file, and a call site that holds a
+ * null passes it on as null.
+ *
+ * IT PAIRS WITH {@link ConnectedAppBroker.authorizations}: a broker declaring `null` there
+ * answers `null` here, and a broker declaring a flow answers a non-null id on every successful
+ * mint. A broker that grew a flow and forgot the id would leave D24 with no source while every
+ * type still checked, so `broker-authorization-pairing.test.ts` asserts the pair over every
+ * broker literal in the tree.
*/
authorize(request: {
account: AccountRef;
@@ -426,7 +686,19 @@ export type ConnectedAppBroker = {
* address through that before it hands it here.
*/
returnUrl: string;
- }): Promise<{ redirectUrl: string }>;
+ /**
+ * The one action this consent is being begun for, for a vendor that grants per action.
+ *
+ * OPTIONAL, AND IGNORABLE. An adapter whose vendor grants per app, as Composio does, has no use
+ * for it and drops it without throwing: handing an action to a broker that cannot use it is
+ * not an error, and a caller need not know which kind of broker it holds to call this.
+ *
+ * `returnUrl` ABOVE STAYS REQUIRED ON THE SEAM EVEN SO. An adapter whose vendor answers with
+ * its own return leg may ignore it, but the seam does not relax for that: the next adapter
+ * that needs it must not find the field optional because the last one did not.
+ */
+ action?: string;
+ }): Promise<{ redirectUrl: string; authorizationId: string | null }>;
/** Whether this account is currently attached to this app at the vendor. */
isConnected(request: {
account: AccountRef;
@@ -615,11 +887,22 @@ export class BrokerRefusalError extends Error {
* message where no call was made at all.
*/
export class BrokerUnconfiguredError extends BrokerRefusalError {
- constructor(provider = "Composio") {
+ /**
+ * BOTH ARGUMENTS ARE REQUIRED, AND THAT IS THE FIX. This used to default to Composio, so a call
+ * site that passed nothing put `COMPOSIO_API_KEY` on an Arcade deployment. An omission is a
+ * compile error now: every site must say which provider it means and who is reading.
+ *
+ * `reader` IS WHO THE SENTENCE IS FOR. An `operator` can set an environment variable, so the
+ * sentence names the ACTIVE provider's variable (from {@link BROKERED_PROFILE}). A `person` can
+ * set nothing, and a vendor they do not use and a key they cannot reach is the vendor
+ * vocabulary spec D11 keeps off their screen, so theirs names neither.
+ */
+ constructor(provider: BrokerProvider, reader: "operator" | "person") {
+ const { vendor, envVar } = BROKERED_PROFILE[provider];
super(
- provider === "Composio"
- ? "Composio is not configured for this deployment, so nothing was asked. Set COMPOSIO_API_KEY to make the brokered apps available; until it is set there is nothing to connect, nothing to grant and no Composio tool for a Bot to call, and the admin Plugins page shows one row under More apps that goes nowhere."
- : `${provider} is not configured for this deployment, so nothing was asked.`,
+ reader === "person"
+ ? "Connected apps are not set up for this deployment, so nothing was asked. An administrator can set them up."
+ : `${vendor} is not configured for this deployment, so nothing was asked. Set ${envVar} to make the brokered apps available; until it is set there is nothing to connect, nothing to grant and no ${vendor} tool for a Bot to call, and the admin Plugins page shows one row under More apps that goes nowhere.`,
);
this.name = "BrokerUnconfiguredError";
}
@@ -628,20 +911,50 @@ export class BrokerUnconfiguredError extends BrokerRefusalError {
/**
* A return address that could not bring anybody back, refused before a consent is spent on it.
*
- * ITS OWN CLASS BECAUSE ITS REMEDY IS ITS OWN. Every other refusal in this file is about Composio —
- * a key that is not set, a config this deployment never made, a consent the vendor answered with no
- * page. This one is about this deployment's own address for itself, and the person who can act on it
- * is an operator with `OPENBOT_APP_URL` in front of them. A caller that could not tell the two apart
- * would send somebody to check a Composio key that is perfectly fine.
+ * ITS OWN CLASS BECAUSE ITS REMEDY IS ITS OWN. Every other refusal in this file is about the broker
+ * — a key that is not set, a config this deployment never made, a consent the vendor answered with
+ * no page. This one is about this deployment's own address for itself, and the reader who can act
+ * on it is an operator with `OPENBOT_APP_URL` in front of them. A caller that could not tell the
+ * two apart would send somebody to check a vendor key that is perfectly fine.
*
* A {@link BrokerRefusalError} because it keeps that class's promise about the message: the sentence
* is written here, names the step that fixes it, and carries no url. Which matters more than usual
* for this one — the value it is refusing is the thing a bad message would be tempted to quote, and
* an address is the half of a connect link that says which deployment and which person it is for.
+ *
+ * TWO SENTENCES, BECAUSE THE READER WHO MEETS THIS IS NOT THE READER WHO CAN FIX IT, and that is
+ * the whole of what this class now settles. Both call sites are `requireUser` routes: whoever reads
+ * `message` is somebody who pressed Connect, and `OPENBOT_APP_URL` is an environment variable they
+ * cannot set and a vendor name is vocabulary spec D11 keeps off their screen — the same division
+ * {@link BrokerUnconfiguredError} draws between its `person` and its `operator`. So `message` says
+ * what happened and who to ask, and {@link operatorMessage} — which the route logs rather than
+ * answers — names the ACTIVE provider and the variable. A single sentence had to fail one of them,
+ * and the one it failed was the person: it spelled `Composio` as a literal on a function that took
+ * no provider, so the Arcade deployments that are the only ones able to reach the authorize route's
+ * refusal were told their failure involved a vendor they do not use.
+ *
+ * THE OPERATOR'S HALF IS HELD AND NOT RAISED, so {@link brokerSentence} keeps answering with the
+ * safe sentence and a route that passes a refusal through cannot pass this one through by accident.
+ * Neither half quotes the address, for the reason given on {@link brokerReturnUrl}: a log line is
+ * one of the places that message was always promised not to reach.
*/
export class BrokerReturnUrlError extends BrokerRefusalError {
- constructor(message: string) {
- super(message);
+ /**
+ * The same refusal written for whoever can act on it, for a log and never for a response body.
+ */
+ readonly operatorMessage: string;
+
+ constructor(provider: BrokerProvider, address: "unbuilt" | "not-a-web-page") {
+ const { vendor } = BROKERED_PROFILE[provider];
+ super(
+ address === "unbuilt"
+ ? "This deployment built no address to send you back to, so connecting was not begun rather than begun with nowhere to land. Nothing was asked of your account; an administrator can set this deployment's address up."
+ : "The address this deployment would send you back to is not a web address, so a consent granted now would end on a page with no way back here. Nothing was asked of your account; an administrator can correct this deployment's address.",
+ );
+ this.operatorMessage =
+ address === "unbuilt"
+ ? `This deployment built no address for ${vendor} to send a person back to, so the connection was not begun rather than begun with nowhere to land. Set OPENBOT_APP_URL to the address this deployment's pages are served from, and connecting an app will have a return leg.`
+ : `The address ${vendor} would send a person back to is not a web address, so a consent granted there would end on ${vendor}'s own page with no way back here. Set OPENBOT_APP_URL to this deployment's own origin including the scheme — https://openbot.example.com rather than openbot.example.com.`;
this.name = "BrokerReturnUrlError";
}
}
@@ -658,21 +971,24 @@ export class BrokerReturnUrlError extends BrokerRefusalError {
* TWO REFUSALS, BECAUSE THEY ARE TWO DIFFERENT MISTAKES. An empty address is a caller that built
* none — the guard in front of this one did not run, or ran against the wrong value. An address that
* is not a web page is a configured one that cannot work: `OPENBOT_APP_URL` set to
- * `openbot.example.com`, which no browser can resolve from Composio's origin, or to `localhost:3001`,
+ * `openbot.example.com`, which no browser can resolve from the vendor's origin, or to `localhost:3001`,
* where `localhost:` is read as the scheme. Both are reachable from the settings this deployment
* actually ships — the variable is an environment string and nothing between it and the vendor looks
* at it — and both end with the same person on the same hosted page with nowhere to go.
*
* IT NAMES THE SETTING AND NOT THE VALUE. The setting is the remedy, and it is the same one whether
* the address arrived empty or malformed; the value is a page address for one person's connection
- * and belongs in no message, no log and no audit row, for the reason the connect url does not.
+ * and belongs in no message, no log and no audit row, for the reason the connect url does not. And
+ * it names it to the OPERATOR ALONE: the provider passed in picks that reader's vendor, while the
+ * sentence the person reads names neither it nor the variable, which {@link BrokerReturnUrlError}
+ * argues at length.
*
* WHAT COMES BACK IS THE ADDRESS THAT WAS CHECKED, WHICH IS NOT ALWAYS THE STRING THAT WENT IN. The
* check reads a parsed address: the emptiness test trims, and parsing drops the spaces and control
* characters a URL cannot contain — so ` https://openbot.test/…`, an address ending in a newline and
* one with a tab inside its host all satisfy this guard while denoting something else entirely. A
* version that approved the parsed address and returned the raw one approved nothing: the padding
- * travelled on to Composio as part of the callback, which is the person stranded on a vendor page
+ * travelled on to the vendor as part of the callback, which is the person stranded on a vendor page
* that this function exists to prevent. Returning what was read is what makes the reading binding.
*
* THAT IS A READING OF THE ADDRESS AND NOT A CHOICE ABOUT IT. Where somebody lands holding a
@@ -682,17 +998,16 @@ export class BrokerReturnUrlError extends BrokerRefusalError {
* at an address that means nothing, which is why the branch above refuses rather than repairs: a
* missing scheme is a setting to fix and not whitespace to drop.
*/
-export function brokerReturnUrl(returnUrl: string): string {
+export function brokerReturnUrl(
+ returnUrl: string,
+ provider: BrokerProvider,
+): string {
if (returnUrl.trim() === "") {
- throw new BrokerReturnUrlError(
- "This deployment built no address for Composio to send you back to, so the connection was not begun rather than begun with nowhere to land. Set OPENBOT_APP_URL to the address this deployment's pages are served from, and connecting an app will have a return leg.",
- );
+ throw new BrokerReturnUrlError(provider, "unbuilt");
}
const address = webAddress(returnUrl);
if (address === null) {
- throw new BrokerReturnUrlError(
- "The address Composio would send you back to is not a web address, so a consent granted there would end on Composio's own page with no way back here. Set OPENBOT_APP_URL to this deployment's own origin including the scheme — https://openbot.example.com rather than openbot.example.com.",
- );
+ throw new BrokerReturnUrlError(provider, "not-a-web-page");
}
return address.href;
}
@@ -734,3 +1049,134 @@ function webAddress(value: string): URL | null {
export function brokerSentence(error: unknown): string | null {
return error instanceof BrokerRefusalError ? error.message : null;
}
+
+/** What configuration says about which broker is active, INCLUDING why it says nothing. */
+export type ProviderDecision =
+ | { kind: "active"; provider: BrokerProvider }
+ /** No key is set: this deployment has no broker and never had one through configuration. */
+ | { kind: "unconfigured" }
+ /**
+ * Both keys are set and `OPENBOT_BROKER_PROVIDER` names neither, so configuration proposes two
+ * providers and chooses between them nowhere. A legal state — `docs/plugins/arcade.md` says so,
+ * and a migrating deployment passes through it — and the one an answer must never be invented
+ * for.
+ */
+ | { kind: "ambiguous"; configured: BrokerProvider[] }
+ /** `OPENBOT_BROKER_PROVIDER` names a provider whose key is unset: an operator's typo. */
+ | { kind: "misnamed"; named: BrokerProvider };
+
+/**
+/**
+ * A KEY OF NOTHING IS NO KEY.
+ *
+ * `ARCADE_API_KEY=` with nothing after it, or a value that is all whitespace, is an operator typo
+ * and not a configured credential. Exported because {@link providerDecision}, {@link activeProvider}
+ * and `./provider-wiring`'s ambiguity refusal must agree on it exactly: wiring decides that both
+ * keys are set and the deployment cannot say which vendor is live, and if it counted a blank key as
+ * set while these did not, it would tell an operator holding two EMPTY keys that both are
+ * configured. One definition, so they cannot drift apart.
+ *
+ * `./config` trims every optional environment value and so happens to turn those into `undefined`
+ * today, but that is one incidental guard in a distant module standing between a typo and a client
+ * built on "", and these functions take config-shaped VALUES rather than an environment, so every
+ * other caller (a test, an embedder, a future loader) reaches them untrimmed. What "configured"
+ * means for a credential is answered here.
+ */
+export const credential = (key: string | undefined): boolean =>
+ key !== undefined && key.trim() !== "";
+
+/**
+ * Which broker this deployment runs, from configuration alone (no database access), AND WHY WHEN
+ * NOTHING DOES.
+ *
+ * THE THREE CAUSES ARE NOT ONE CAUSE, which is why this answers a decision rather than
+ * `BrokerProvider | null`. "No key set" is a deployment with no broker and no brokered row of its
+ * own; "both keys, nothing named" is a migration in progress, with live rows at one vendor or the
+ * other and nothing in configuration saying which; "named but unkeyed" is a typo that must stop the
+ * server. A single `null` made all three the same value, and every caller that then wrote
+ * `?? "composio"` turned the second one — the only one that can hold Arcade rows — into "Composio
+ * is active", which filtered those rows out of existence and left the accounts behind them
+ * unrevokable.
+ *
+ * One key set selects that provider. Both set is legal and expected, since ending an account at the
+ * old vendor needs its key, so `brokerProvider` decides between them.
+ */
+export function providerDecision(c: {
+ composioApiKey?: string;
+ arcadeApiKey?: string;
+ brokerProvider?: BrokerProvider;
+}): ProviderDecision {
+ const configured: BrokerProvider[] = [];
+ if (credential(c.composioApiKey)) configured.push("composio");
+ if (credential(c.arcadeApiKey)) configured.push("arcade");
+ const named = c.brokerProvider;
+ if (named !== undefined) {
+ return configured.includes(named)
+ ? { kind: "active", provider: named }
+ : { kind: "misnamed", named };
+ }
+ if (configured.length === 1)
+ return { kind: "active", provider: configured[0] as BrokerProvider };
+ return configured.length === 0
+ ? { kind: "unconfigured" }
+ : { kind: "ambiguous", configured };
+}
+
+/**
+ * The active provider, or `null` for every deployment where configuration names none.
+ *
+ * KEPT FOR THE CALLERS THAT GENUINELY ASK A YES/NO QUESTION — "is there a broker to wire" — and
+ * nothing more. A caller that has to NAME a provider when this answers null is asking the question
+ * {@link providerDecision} answers, and must take the three causes apart instead of defaulting one
+ * of them to a vendor.
+ */
+export function activeProvider(c: {
+ composioApiKey?: string;
+ arcadeApiKey?: string;
+ brokerProvider?: BrokerProvider;
+}): BrokerProvider | null {
+ const decision = providerDecision(c);
+ return decision.kind === "active" ? decision.provider : null;
+}
+
+/**
+ * Activating a provider is refused while the OTHER provider still holds `brokered_connections` rows.
+ *
+ * KEYED ON ROWS, NOT ON KEYS, AND THAT IS THE WHOLE POINT. Both keys set is a legal and expected
+ * state, because ending an account at the old vendor needs its key, so a deployment holding both is
+ * never misconfigured. What a deployment must not be able to do is switch by editing its environment:
+ * `DELETE /servers/:id/connection` revokes at the vendor before it deletes anything local, and a
+ * deployment that swapped keys would hold rows for accounts it could no longer end.
+ *
+ * TAKES A COUNT, NOT A DATABASE. The caller counts the rows belonging to the provider that is not
+ * `candidate`; this reads no key, never calls {@link activeProvider} and imports no vendor package.
+ */
+export function assertActivatable(
+ candidate: BrokerProvider,
+ otherProviderRows: number,
+): void {
+ const other = candidate === "arcade" ? "Composio" : "Arcade";
+ const self = candidate === "arcade" ? "Arcade" : "Composio";
+ /*
+ * A COUNT THAT IS NOT A COUNT REFUSES, AND IS NEVER REPEATED BACK. `NaN <= 0` is false, so a
+ * `<= 0` test alone lets a NaN through to the sentence below, where it reads as the literal
+ * words "NaN brokered connections" — a refusal that tells a person a number they cannot act on.
+ * A negative or fractional count is the same failure wearing different clothes: whatever
+ * produced it was not counting rows. So the only counts allowed past here are the ones a row
+ * count can actually be, and anything else refuses saying what is true — the count was not
+ * taken — rather than printing the sentinel it was handed.
+ *
+ * REFUSING, NOT PROCEEDING, because this guard exists to keep a deployment from reaching the
+ * vendor with rows it could no longer revoke. Not knowing how many rows the other provider
+ * holds is not the same as knowing it holds none.
+ */
+ if (!Number.isInteger(otherProviderRows) || otherProviderRows < 0) {
+ throw new BrokerRefusalError(
+ `${self} cannot be activated: the connections belonging to ${other} could not be counted. Keep both API keys set, check that the database is reachable, and start again; activating without that count could leave accounts nobody can revoke.`,
+ );
+ }
+ if (otherProviderRows === 0) return;
+ throw new BrokerRefusalError(
+ `${self} cannot be activated while ${otherProviderRows} brokered connection${otherProviderRows === 1 ? "" : "s"} still belong${otherProviderRows === 1 ? "s" : ""} to ${other}. Keep both API keys set, and end those connections through the ${other} sunset flow first; switching providers by changing the environment would leave accounts nobody can revoke.`,
+ );
+}
diff --git a/server/src/plugins/composio-adapter.ts b/server/src/plugins/composio-adapter.ts
index 716db9e4c..5cba2e202 100644
--- a/server/src/plugins/composio-adapter.ts
+++ b/server/src/plugins/composio-adapter.ts
@@ -109,7 +109,7 @@ export function buildComposioClient(
const broker: ComposioBroker = {
...directoryMethods(context),
- ...consentMethods(context),
+ ...consentMethods(context, actions),
...fieldMethods(context),
...accountMethods(context),
};
diff --git a/server/src/plugins/composio-client/consent.ts b/server/src/plugins/composio-client/consent.ts
index eba5bb582..e14943ace 100644
--- a/server/src/plugins/composio-client/consent.ts
+++ b/server/src/plugins/composio-client/consent.ts
@@ -1,6 +1,12 @@
-import { BrokerRefusalError, type ComposioBroker } from "../broker";
+import {
+ BrokerRefusalError,
+ type ComposioBroker,
+ type GrantState,
+} from "../broker";
+import { type ComposioActions, LISTING_LIMIT } from "../composio";
import {
askVendor,
+ configIdOf,
everyRefusal,
named,
sent,
@@ -11,18 +17,130 @@ import {
withdrawableAccounts,
withdrawalDeclined,
} from "../composio-vendor";
+import type { AccountRef } from "../shared-accounts";
import type { ClientContext } from "./context";
import { CONFIG_SUFFIX, CONNECTED, REVOCABLE, STATUSES_NAMED } from "./model";
-/** Sending a person to consent, asking whether they are connected, and revoking. */
-export function consentMethods(context: ClientContext) {
+/**
+ * Sending a person to consent, asking whether they are connected and what they hold, and revoking.
+ *
+ * `actions` is the same client's action seam, read by {@link ComposioBroker.grantsFor} for the
+ * actions it reports a grant over.
+ */
+export function consentMethods(
+ context: ClientContext,
+ actions: ComposioActions,
+) {
const { vendor, accountsFor, configsFor, askForEach } = context;
return {
+ // Composio's grant is held by this deployment. Its existing behaviour, declared; see
+ // {@link ConnectedAppBroker.grantIsLocal} for why this is a property and not a comparison.
+ grantIsLocal: true,
+ // Composio runs no correlated, pollable authorization flow: it mints nothing to poll, and its
+ // return leg is an OAuth callback with state, handled in `oauth.ts` and never through this
+ // seam. Declared rather than omitted; see {@link ConnectedAppBroker.authorizations}.
+ authorizations: null,
+ /**
+ * The toolkit itself, WHICH IS AN ANSWER AND NOT A STUB.
+ *
+ * A Composio connection is made against an auth config, and an auth config is per toolkit. So
+ * there is no auth provider standing behind several toolkits here the way Arcade's `google`
+ * stands behind `Gmail` and `GoogleCalendar`: the toolkit IS its own auth provider, and the set
+ * of apps sharing a connection with it is consequently ALWAYS EMPTY. That is the truth about
+ * Composio rather than a degradation of it — {@link ConnectedAppBroker.grantsFor}'s "degenerate
+ * is not absent", one member along.
+ *
+ * SO IT DOES NOT THROW, and the reason is what a throw would cost the callers: every consumer
+ * of the sibling computation would need a presence test around this call, and the disconnect
+ * path would carry a provider branch — which is the vendor-name comparison this seam exists to
+ * keep out of permission and revocation code.
+ *
+ * No network: the answer is a property of how Composio is shaped, so nothing is asked.
+ */
+ authProviderOf: async (toolkit: string) => toolkit,
+ /**
+ * What this person holds at this app, said once per action because that is what Composio knows.
+ *
+ * DEGENERATE AND NOT ABSENT, which is why this is implemented rather than thrown. An auth
+ * config fixes its scope set when it is created and a connection is made against the config,
+ * so there is exactly one answer for the toolkit: either this person has an account on it or
+ * they have not, and the scopes are the config's. Reporting that answer once per action is the
+ * honest rendering of a toolkit-wide grant through {@link GrantState}, which is this
+ * deployment's vocabulary rather than any vendor's row shape. The alternative — a throw —
+ * would make one seam member answer two different questions depending on who implemented it,
+ * which is the defect this file already carried at {@link ComposioBroker.accountName}.
+ *
+ * THE STATE IS MEASURED OVER EVERY CONFIG, AS {@link ComposioBroker.isConnected}'S IS. The two
+ * are the same read-only question — can this person act through the app — and the call that
+ * runs an action names the person and the toolkit, not the account, so an account on an
+ * operator's hand-made config is one they act through. Measuring this one over our configs
+ * alone would answer `needs-consent` to somebody the gate lets through, and send them to
+ * consent to an app they already use. Only {@link ComposioBroker.revoke}, which ACTS on what it
+ * finds, is scoped to ours.
+ *
+ * SO THE SCOPES ARE THE CONFIGS THIS PERSON'S ACCOUNTS WERE MINTED AGAINST, WHICH NEED NOT BE
+ * OURS. A config fixes its scope set when it is created and every account on it holds exactly
+ * that set, so an `authorized` answer carries the scopes of the configs the counted accounts
+ * sit on — every one of them, which is why the listing is read to the end rather than stopped at
+ * the first row. This used to carry THIS DEPLOYMENT'S scopes whatever the account sat on, which
+ * paired `authorized` with a scope set the person never granted. A `needs-consent` answer
+ * carries our configs' scopes instead, because those are the configs a consent from here is
+ * minted against: it describes what they would be asked for.
+ *
+ * AN ACCOUNT WHOSE CONFIG CANNOT BE NAMED, OR IS NOT AMONG THE CONFIGS THAT COULD BE READ,
+ * LENDS NO SCOPES — neither its own, which nothing here can read, nor ours, which it does not
+ * hold. The names are deduplicated and ordered so that two readings of one unchanged deployment
+ * are one answer.
+ */
+ async grantsFor({
+ toolkit,
+ account,
+ }: {
+ toolkit: string;
+ account: AccountRef;
+ }): Promise {
+ const connected = await accountsFor(
+ account.vendorUserId,
+ toolkit,
+ CONNECTED,
+ );
+ const state = connected.length > 0 ? "authorized" : "needs-consent";
+
+ const { held, ours } = await configsFor(toolkit);
+ const mintedOn = new Set(
+ connected.map(configIdOf).filter((id) => id !== null),
+ );
+ const granting =
+ state === "authorized"
+ ? held.filter((config) => mintedOn.has(config.id))
+ : ours;
+ const scopes = [
+ ...new Set(granting.flatMap((config) => config.scopes)),
+ ].sort();
+
+ const published = await actions.listActions(toolkit, {
+ limit: LISTING_LIMIT,
+ });
+ // A fresh array per entry: one shared list would let a caller mutating its first grant's
+ // scopes rewrite every other grant in the answer. An authorized entry is always `held`: it is
+ // authorized only because this person holds an account, which is a consent they gave.
+ return published.map(
+ (action): GrantState =>
+ state === "authorized"
+ ? {
+ action: action.slug,
+ state,
+ scopes: [...scopes],
+ authorization: "held",
+ }
+ : { action: action.slug, state, scopes: [...scopes] },
+ );
+ },
async authorize({
account,
toolkit,
returnUrl,
- }): Promise<{ redirectUrl: string }> {
+ }): Promise<{ redirectUrl: string; authorizationId: string | null }> {
const userId = account.vendorUserId;
/*
* THE CONFIG THIS DEPLOYMENT ALREADY MADE, AND NO SECOND ONE MADE HERE.
@@ -291,7 +409,11 @@ export function consentMethods(context: ClientContext) {
`Composio began a connection to ${toolkit} but answered with no page to visit, so there is nothing to send ${whose(account)} to. An app that is connected by entering a credential rather than by visiting a page cannot be connected from here.`,
);
}
- return { redirectUrl: page };
+ // DECLARED NULL, NOT COMPOSIO'S CONNECTION-REQUEST ID. Composio's return leg never comes
+ // through this seam: it lands in `oauth.ts`, correlated by sealed state this deployment
+ // minted, not by an id the broker handed back. The connection-request id would put a value
+ // with a DIFFERENT MEANING behind the same member — the `accountName` defect again.
+ return { redirectUrl: page, authorizationId: null };
},
async isConnected({ account, toolkit }): Promise {
@@ -578,5 +700,14 @@ export function consentMethods(context: ClientContext) {
*/
return ids.length > 0;
},
- } satisfies Pick;
+ } satisfies Pick<
+ ComposioBroker,
+ | "grantIsLocal"
+ | "authorizations"
+ | "authProviderOf"
+ | "grantsFor"
+ | "authorize"
+ | "isConnected"
+ | "revoke"
+ >;
}
diff --git a/server/src/plugins/composio-client/context.ts b/server/src/plugins/composio-client/context.ts
index 9953919a3..57f0d770b 100644
--- a/server/src/plugins/composio-client/context.ts
+++ b/server/src/plugins/composio-client/context.ts
@@ -1,6 +1,7 @@
import type { BrokerApp, BrokerRefusalError } from "../broker";
import { LISTING_LIMIT } from "../composio";
import {
+ type AccountRow,
appOf,
askVendor,
type CheckedAuthConfig,
@@ -34,8 +35,8 @@ export function createClientContext(
*
* AND SO ARE THE OTHER TWO THINGS THE TWO QUESTIONS DO NOT SHARE, for the same reason the
* statuses are. `configs` is which authorization configs the answer is about — every one of them
- * for the gate, and only this deployment's for the withdrawal, because the withdrawal ACTS on
- * what it finds. `enough` is when the rows in hand already settle the question, which is true of
+ * for the three that only READ (the gate, `grantsFor` and `accountName`), and only this
+ * deployment's for the withdrawal, because the withdrawal ACTS on what it finds. `enough` is when the rows in hand already settle the question, which is true of
* a boolean the moment one row arrives and never true of a set of accounts to end. Both are
* written at the call sites below, beside the statuses, so that the whole of the difference
* between the two questions is one argument list a reader can compare.
@@ -46,9 +47,9 @@ export function createClientContext(
statuses: VendorAccountStatus[],
asked: {
configs?: string[];
- enough?: (rows: { id?: unknown; data?: unknown }[]) => boolean;
+ enough?: (rows: AccountRow[]) => boolean;
} = {},
- ): Promise<{ id?: unknown; data?: unknown }[]> => {
+ ): Promise => {
/*
* EVERY PAGE UNLESS THE CALLER SAYS OTHERWISE, READ HERE RATHER THAN AT EITHER CALLER, so that
* the two questions cannot drift on the one thing they do share. A truncated listing is the
diff --git a/server/src/plugins/composio-client/model.ts b/server/src/plugins/composio-client/model.ts
index d2619357a..47cb41199 100644
--- a/server/src/plugins/composio-client/model.ts
+++ b/server/src/plugins/composio-client/model.ts
@@ -1,6 +1,7 @@
import type { BrokerApp, FieldScheme } from "../broker";
import type { ComposioResult } from "../composio";
import type {
+ AccountRow,
CheckedAuthConfig,
VendorAccountStatus,
VendorAuthConfig,
@@ -420,7 +421,7 @@ export type ComposioVendor = {
* same function reads `response.auth_config.id` (`:60`) on its way to building each row, so a
* row that is not an object raises inside the vendor's code and never reaches this listing.
*/
- items: { id?: unknown; data?: unknown }[];
+ items: AccountRow[];
/**
* The same truncation signal as the auth-config listing above, from the same vendor schema.
*
diff --git a/server/src/plugins/composio-vendor.ts b/server/src/plugins/composio-vendor.ts
index cc8f4e9d4..9e29f74fd 100644
--- a/server/src/plugins/composio-vendor.ts
+++ b/server/src/plugins/composio-vendor.ts
@@ -178,8 +178,42 @@ export type VendorAuthConfig = {
id?: unknown;
name?: unknown;
status?: unknown;
+ /**
+ * What the config was created to ask for, where Composio publishes it, and `unknown` because it
+ * is copied across verbatim like the three above.
+ *
+ * IT IS A PROPERTY OF THE CONFIG RATHER THAN OF AN ACCOUNT, which is the whole of why
+ * {@link ComposioBroker.grantsFor}'s answer is one state for a toolkit: the scope set is fixed
+ * when the config is created and every account connected against it holds exactly that set.
+ */
+ scopes?: unknown;
};
+/**
+ * One connected account as the listing hands it over, narrowed to the fields this file reads.
+ *
+ * `id` and `data` are read by the withdrawal and by {@link ComposioBroker.accountName}.
+ * `authConfig` is read by {@link ComposioBroker.grantsFor} alone, for the id of the config the
+ * account was minted against, because that config — and no other — fixes the scopes the account
+ * holds. `transformConnectedAccountResponse` builds it on every row from the raw
+ * `auth_config` (`@composio/core` 0.18.1, `src/utils/transformers/connectedAccounts.ts:55-62`), and
+ * it is `unknown` for the reason every other field here is: the transform is warn-only.
+ */
+export type AccountRow = { id?: unknown; data?: unknown; authConfig?: unknown };
+
+/**
+ * The id of the auth config one account was minted against, or null where Composio did not name it.
+ *
+ * NULL RATHER THAN A REFUSAL, BECAUSE ITS ONE READER REPORTS RATHER THAN ACTS — the reasoning of
+ * {@link scopesOf}, one field along. An account whose config cannot be named is still an account,
+ * and what is lost is only the description of what it holds.
+ */
+export function configIdOf(row: AccountRow): string | null {
+ const config = row.authConfig;
+ if (!config || typeof config !== "object") return null;
+ return textOf((config as { id?: unknown }).id);
+}
+
/**
* The connected-account statuses this file knows how to ask for, as the literals the SDK admits.
*
@@ -1038,6 +1072,16 @@ export function appOf(row: VendorToolkit, position: number): BrokerApp {
logo,
categories,
actionCount,
+ /*
+ * THREE EXPLICIT NULLS, AND NEVER `?? 0`. Composio's toolkit listing publishes no behaviour
+ * classification and no per-action scope sets, so this deployment has not measured them here —
+ * which is a different fact from having measured them and found none. A zero would say the
+ * second, and the page would fire its coverage warning on every Composio app: a false alarm at
+ * exactly the moment somebody is deciding whether to trust one. See {@link BrokerApp}.
+ */
+ classifiedCount: null,
+ permissionCount: null,
+ scopedActionCount: null,
connection: connectionOf(row),
};
}
@@ -1055,8 +1099,35 @@ export type CheckedAuthConfig = {
id: string;
name: string;
status: unknown;
+ /** The scope set this config was created with, as {@link scopesOf} could read it. */
+ scopes: string[];
};
+/**
+ * The scope names on an auth config row, and the EMPTY LIST for everything else.
+ *
+ * NOT A REFUSAL, BECAUSE ITS ONE READER REPORTS RATHER THAN ACTS. Every other field this file
+ * reads off a config decides something destructive or something a person is sent through — which
+ * id to delete, which config to mint a consent against — so a shape that cannot be read is a thing
+ * to stop on. Scopes are carried on a {@link GrantState}, which is a description handed to a page:
+ * Composio publishes none at all for a key-scheme config, so "no scope set published" is the
+ * ordinary case and not a fault, and an app somebody is plainly connected to must not become
+ * unreportable because the field arrived as a string.
+ *
+ * ONE PASS, AND THE NON-STRINGS ARE DROPPED RATHER THAN RENDERED. A scope is a name sent to a
+ * provider; `String({})` is not one, and a list half of whose entries were invented here is worse
+ * than a shorter list.
+ */
+function scopesOf(value: unknown): string[] {
+ if (!Array.isArray(value)) return [];
+ const names: string[] = [];
+ for (const entry of value) {
+ const name = textOf(entry);
+ if (name !== null) names.push(name);
+ }
+ return names;
+}
+
/**
* That status as the WORD it is, so the test and the sentence about it are about the same value.
*
@@ -1198,7 +1269,12 @@ export function readableConfigs(
return;
}
- const config: CheckedAuthConfig = { id, name, status: row.status };
+ const config: CheckedAuthConfig = {
+ id,
+ name,
+ status: row.status,
+ scopes: scopesOf(row.scopes),
+ };
if (slot === undefined) {
slotOf.set(id, settled.length);
settled.push(config);
diff --git a/server/src/plugins/google-drive-rest.ts b/server/src/plugins/google-drive-rest.ts
index 59ad4f943..a57b3da0b 100644
--- a/server/src/plugins/google-drive-rest.ts
+++ b/server/src/plugins/google-drive-rest.ts
@@ -65,12 +65,17 @@ const SHARED_DRIVE_ITEMS = {
*
* A Doc has no bytes to download — `alt=media` refuses it — so it has to be exported. Anything not
* in here is a real file and is fetched directly.
+ *
+ * A Map, because the type comes off a file somebody else uploaded and named. Asked of a plain
+ * object, `toString` is answered with `Object.prototype.toString`: truthy, so the file is taken for
+ * an editor format, the refusal below is skipped, and that function is sent to Google as the format
+ * to export to.
*/
-const EXPORTABLE: Record = {
- "application/vnd.google-apps.document": "text/plain",
- "application/vnd.google-apps.spreadsheet": "text/csv",
- "application/vnd.google-apps.presentation": "text/plain",
-};
+const EXPORTABLE: ReadonlyMap = new Map([
+ ["application/vnd.google-apps.document", "text/plain"],
+ ["application/vnd.google-apps.spreadsheet", "text/csv"],
+ ["application/vnd.google-apps.presentation", "text/plain"],
+]);
/**
* What this adapter offers, as the same shape a server would have answered `tools/list` with.
@@ -478,7 +483,7 @@ export async function callTool(
readId = targetId;
}
- const exportAs = file.mimeType ? EXPORTABLE[file.mimeType] : undefined;
+ const exportAs = file.mimeType ? EXPORTABLE.get(file.mimeType) : undefined;
/*
* A file whose bytes are not text is declined by name, not decoded and hoped for.
diff --git a/server/src/plugins/logos.ts b/server/src/plugins/logos.ts
index c2f4a9936..604d686e7 100644
--- a/server/src/plugins/logos.ts
+++ b/server/src/plugins/logos.ts
@@ -1,25 +1,26 @@
import { and, eq, isNull } from "drizzle-orm";
import type { Database } from "../db/client";
import { mcpServers } from "../db/schema";
+import { type BrokerProvider, toolkitOfFor } from "./access";
import type { ComposioBroker } from "./broker";
-import { toolkitOf } from "./composio";
/** Fill older app records without changing their connections, schemes, or grants. */
-export async function backfillComposioLogos(
+export async function backfillBrokeredLogos(
database: Database,
+ provider: BrokerProvider,
broker: Pick,
) {
const missing = await database
.select({ id: mcpServers.id, url: mcpServers.url })
.from(mcpServers)
- .where(and(eq(mcpServers.provenance, "composio"), isNull(mcpServers.logo)));
+ .where(and(eq(mcpServers.provenance, provider), isNull(mcpServers.logo)));
if (missing.length === 0) return;
const logos = new Map(
(await broker.listApps()).map((app) => [app.slug, app.logo]),
);
for (const row of missing) {
- const slug = toolkitOf(row.url);
+ const slug = toolkitOfFor(provider, row.url);
const logo = slug ? logos.get(slug) : null;
if (!logo) continue;
await database
@@ -29,7 +30,7 @@ export async function backfillComposioLogos(
and(
eq(mcpServers.id, row.id),
eq(mcpServers.url, row.url),
- eq(mcpServers.provenance, "composio"),
+ eq(mcpServers.provenance, provider),
isNull(mcpServers.logo),
),
);
diff --git a/server/src/plugins/mcp.ts b/server/src/plugins/mcp.ts
index d2d310def..b2265c1ca 100644
--- a/server/src/plugins/mcp.ts
+++ b/server/src/plugins/mcp.ts
@@ -192,6 +192,20 @@ export type McpTool = {
* `McpTool` stays exactly what a `tools/list` answer contains, because that is what it is for.
*/
export type ListedTool = McpTool & {
+ /**
+ * The vendor's own spelling, when it differs from the name this deployment records.
+ *
+ * ABSENT MEANS THEY ARE THE SAME STRING, which is the honest answer for every transport but
+ * Arcade: Composio's `GMAIL_FETCH_EMAILS`, an MCP server's names and the Drive REST names are all
+ * offerable to a model exactly as the vendor spells them, so there is nothing to keep apart.
+ *
+ * THE TRANSPORT DECIDES, because only it knows the vendor's naming convention. Arcade's listing
+ * answers `Toolkit.Action` and the dot is illegal in a model tool name, so its transport splits
+ * the two: `name` is the action, this is the qualified name the vendor is called by. Deriving it
+ * here instead would make a rule out of the shape of one vendor's listing. See
+ * {@link mcpTools.vendorName}.
+ */
+ vendorName?: string;
/** What the vendor said this action does, when it said anything. */
effect?: "read" | "write";
/** Whether the vendor marked it as destroying something. */
diff --git a/server/src/plugins/plugin-store/calls.ts b/server/src/plugins/plugin-store/calls.ts
index 70f9e9482..c409223e4 100644
--- a/server/src/plugins/plugin-store/calls.ts
+++ b/server/src/plugins/plugin-store/calls.ts
@@ -133,6 +133,7 @@ export function callMethods(context: StoreContext) {
const advertised = await database
.select({
name: mcpTools.name,
+ vendorName: mcpTools.vendorName,
inputSchema: mcpTools.inputSchema,
effect: mcpTools.effect,
destructive: mcpTools.destructive,
@@ -480,7 +481,20 @@ export function callMethods(context: StoreContext) {
actorId: vendorUserId ?? input.credentialActorId ?? input.actorId,
botId: input.botId,
},
- toolName,
+ /*
+ * THE NAME THE VENDOR KNOWS, WHICH IS THE ONLY ONE IT WILL ANSWER TO. `toolName` is this
+ * deployment's half of the ref and is what the model was offered; Arcade is called by the
+ * qualified name its listing gave. They are the same string for every other transport,
+ * which is what the fallback records rather than papers over — see
+ * {@link mcpTools.vendorName}.
+ *
+ * FALLING BACK TO `toolName` RATHER THAN REFUSING, because a row this read did not find
+ * is not an unknown action: `classifyTool` above is built to decide on an app whose
+ * listing predates its catalogue entry, and the call goes on to the vendor to be judged
+ * there. The fallback is reached only for an action with no recorded row at all, and for
+ * those two names were never distinct.
+ */
+ advertised[0]?.vendorName ?? toolName,
vendorArgs,
);
await recordAuditEvent(auditStore, {
diff --git a/server/src/plugins/plugin-store/connections.ts b/server/src/plugins/plugin-store/connections.ts
index ab748bd12..035fd0d4f 100644
--- a/server/src/plugins/plugin-store/connections.ts
+++ b/server/src/plugins/plugin-store/connections.ts
@@ -1,13 +1,16 @@
-import { and, asc, eq, inArray, sql } from "drizzle-orm";
-import { recordAuditEvent } from "../../audit";
+import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
+import { createAuditStore, recordAuditEvent } from "../../audit";
+import type { Database } from "../../db/client";
import {
brokeredConnections,
credentials as credentialRows,
mcpServers,
mcpTools,
mcpUserCredentials,
+ pendingAuthorizations,
users,
} from "../../db/schema";
+import { BROKERED_PROFILE, toolkitOfFor } from "../access";
import {
BrokerRefusalError,
BrokerUnconfiguredError,
@@ -18,17 +21,19 @@ import {
import {
asksForArguments,
askAction as composioAskAction,
- toolkitOf,
VERSION_ARG,
} from "../composio";
-import type { AccountRef } from "../shared-accounts";
+import { type AccountRef, personAccount } from "../shared-accounts";
import type { StoreContext } from "./context";
import {
+ attemptNamed,
type BrokeredProbe,
IDENTITY_ACTION,
iso,
type OAuthClient,
+ type PendingAuthorization,
PluginRefusedError,
+ pendingAuthorizationFromRow,
} from "./model";
/** OAuth clients and a person's connections, including every brokered (Composio) connection flow. */
@@ -53,6 +58,13 @@ export function connectionMethods(context: StoreContext) {
brokeredAppRowsAt,
brokeredAppScheme,
brokeredAppKind,
+ brokerProvider,
+ closeAuthorizationAttempt,
+ resolveSiblings,
+ consentRevokeReaches,
+ vendorRevocationClaim,
+ siblingsEndedBy,
+ clearEndedSiblings,
} = context;
return {
/**
@@ -359,6 +371,14 @@ export function connectionMethods(context: StoreContext) {
* draw the deployment's shared account as one of them — a deployment row has no `user_id`,
* and the filter keeps that a fact this query states rather than one it happens to rely on.
*/
+ /*
+ * NO PROVIDER PREDICATE HERE, DELIBERATELY, AND THAT IS NOT AN OVERSIGHT. Each row is resolved
+ * below at the ACTIVE provider's canonical url, so another provider's row would be silently
+ * dropped from the page. What keeps that unreachable is the startup activation refusal
+ * (`assertActivatable`, called from `server/src/index.ts`): it will not boot when the
+ * configured provider differs from the one on the existing rows. The provider sunset flow
+ * relaxes that refusal, and whoever builds it must add the `brokerProvider` predicate here.
+ */
const connections = await database
.select({
toolkit: brokeredConnections.app,
@@ -421,13 +441,21 @@ export function connectionMethods(context: StoreContext) {
* join answered and what the missing `serverId` below drops. A person can hold an account at
* an app this deployment has since removed, and the settings page has no row to draw for it.
*/
+ /*
+ * AT THE ACTIVE BROKER'S SCHEME, which is the one {@link brokeredAppRow} resolves every other
+ * url against. Spelled `composio://` here, this read answered an empty map on any other
+ * broker — every `serverId` undefined, every connection dropped, and the connected-accounts
+ * page drawn empty for somebody holding accounts. Fail-closed and visible, and total.
+ */
+ const canonical = (toolkit: string) =>
+ `${BROKERED_PROFILE[brokerProvider].scheme}${toolkit}`;
const named = await brokeredAppRowsAt(
- connections.map((row) => `composio://${row.toolkit}`),
+ connections.map((row) => canonical(row.toolkit)),
);
const rows = connections
.flatMap((row) => {
- const serverId = named.get(`composio://${row.toolkit}`)?.id;
+ const serverId = named.get(canonical(row.toolkit))?.id;
return serverId === undefined ? [] : [{ ...row, serverId }];
})
// By server id, as the join's own `order by` was, so this read and `connectionsFor` hand the
@@ -487,7 +515,7 @@ export function connectionMethods(context: StoreContext) {
.from(mcpServers)
.where(eq(mcpServers.id, serverId))
.limit(1);
- const toolkit = server ? toolkitOf(server.url) : null;
+ const toolkit = server ? toolkitOfFor(brokerProvider, server.url) : null;
if (!toolkit) return null;
const [row] = await database
@@ -504,7 +532,7 @@ export function connectionMethods(context: StoreContext) {
.leftJoin(users, eq(users.id, brokeredConnections.connectedBy))
.where(
and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.app, toolkit),
eq(brokeredConnections.holder, "deployment"),
),
@@ -571,7 +599,7 @@ export function connectionMethods(context: StoreContext) {
.orderBy(asc(mcpServers.id));
const answering = await brokeredAppRowsAt(
marked
- .filter((row) => toolkitOf(row.url) !== null)
+ .filter((row) => toolkitOfFor(brokerProvider, row.url) !== null)
.map((row) => row.url),
);
const shared = marked.filter(
@@ -580,7 +608,7 @@ export function connectionMethods(context: StoreContext) {
return await Promise.all(
shared.map(async (server) => {
- const toolkit = toolkitOf(server.url);
+ const toolkit = toolkitOfFor(brokerProvider, server.url);
const [connection, kind] = await Promise.all([
this.deploymentConnectionFor(server.id),
toolkit ? brokeredAppKind(toolkit) : null,
@@ -702,7 +730,15 @@ export function connectionMethods(context: StoreContext) {
// property of whichever plan the server happened to pick.
const actions = await database
.select({
- name: mcpTools.name,
+ /*
+ * THE VENDOR'S SPELLING, BECAUSE THIS NAME IS CALLED RATHER THAN DISPLAYED. The pair this
+ * answers goes to the transport directly (see `probeConnection`), and a transport answers
+ * only to the name its own listing gave. {@link IDENTITY_ACTION} is written in that same
+ * vocabulary — `_GET_ME`, `_WHOAMI` — so it is matched against this for the same reason.
+ * Identical to `name` on every transport this path actually reaches. See
+ * {@link mcpTools.vendorName}.
+ */
+ name: mcpTools.vendorName,
inputSchema: mcpTools.inputSchema,
effect: mcpTools.effect,
destructive: mcpTools.destructive,
@@ -834,6 +870,11 @@ export function connectionMethods(context: StoreContext) {
* AN UPDATE AND NEVER AN INSERT, and `returning` is how "there was a row" is ANSWERED rather
* than assumed. An update that matches nothing is not a failure — it is the row having been
* deleted while the vendor was being asked — and the caller has a sentence for that.
+ *
+ * `vendor_user_id` IS DELIBERATELY NOT IN THIS `set`. This branch records a CHECK of a
+ * connection that already exists, never a connection; it may not re-point the row at a
+ * different vendor identity, because only a connect or a confirm (the upsert below) is
+ * evidence of a grant held under the identity it names.
*/
const written = await database
.update(brokeredConnections)
@@ -843,10 +884,38 @@ export function connectionMethods(context: StoreContext) {
return { verifiedAt, wrote: written.length > 0 };
}
+ /*
+ * THE VENDOR IDENTITY THIS WRITE'S EVIDENCE IS ABOUT, WRITTEN ON BOTH BRANCHES OF THE UPSERT.
+ *
+ * CRITERION. After this upsert the row names `input.account.vendorUserId`, whatever it named
+ * before.
+ *
+ * REASON. Every caller of the upsert has just heard from the vendor about the account held
+ * under THIS id — a consent, or the vendor confirming it holds an account — and
+ * `connectionTokenFor` sends calls out under the ROW's `vendor_user_id`, never a recomputed
+ * one. A conflict arm that rewrote `verified`/`verified_at` and left the old id standing
+ * wrote a verdict about one account onto a row naming another, and every later call landed
+ * in the account the evidence was not about. The insert always set it; the arms did not.
+ */
+ const connectedAs = input.account.vendorUserId;
await database
.insert(brokeredConnections)
.values({
- provider: "composio",
+ /*
+ * THE BROKER THIS ROW WAS ACTUALLY MINTED AT, which is this store's own, because this
+ * insert only ever runs behind a connection just made at the broker this store holds.
+ *
+ * CRITERION. A row inserted here carries the provider whose grant it stands for, so the
+ * reads keyed on `provider` above find exactly the rows this writer wrote.
+ *
+ * REASON. The literal `composio` here was the one line that could not fail loudly. An
+ * Arcade deployment writing it would read its own rows back through a `composio` filter
+ * and APPEAR TO WORK, while the refusal that is supposed to fire when the other provider
+ * holds rows never fired — nothing would ever be filed under the other name — and the
+ * offboarding sweep, keyed the same way, would sweep a slice of the table that was not
+ * this deployment's. Every symptom of that is silence.
+ */
+ provider: brokerProvider,
app: input.toolkit,
holder: input.account.holder,
userId:
@@ -869,7 +938,7 @@ export function connectionMethods(context: StoreContext) {
brokeredConnections.userId,
],
targetWhere: sql`${brokeredConnections.holder} = 'person'`,
- set,
+ set: { ...set, vendorUserId: connectedAs },
}
: {
target: [brokeredConnections.provider, brokeredConnections.app],
@@ -879,15 +948,560 @@ export function connectionMethods(context: StoreContext) {
// which `set` otherwise never said, leaving the row crediting whoever
// connected it the first time. Added only when this call names a
// `connectedBy` at all, so a re-check or a confirm that passes none does not
- // blank out the administrator an earlier connect already recorded.
+ // blank out the administrator an earlier connect already recorded. That
+ // condition is the point: do NOT make this unconditional.
set: input.connectedBy
- ? { ...set, connectedBy: input.connectedBy ?? null }
- : set,
+ ? {
+ ...set,
+ vendorUserId: connectedAs,
+ connectedBy: input.connectedBy,
+ }
+ : { ...set, vendorUserId: connectedAs },
},
);
return { verifiedAt, wrote: true };
},
+ /**
+ * The other apps this deployment holds a connection for that share THIS app's connection: the
+ * apps on the same vendor identity AND the same AUTH PROVIDER, never the provider alone. The
+ * reason is written out on {@link connectionSiblings}; this is that computation over the rows
+ * this deployment actually holds under the active provider.
+ *
+ * THE BROKER THIS STORE WAS BUILT WITH ANSWERS IT, AND NO CALLER SUPPLIES A RESOLVER. The auth
+ * provider is the vendor's fact and this store keeps no copy of it, so it is asked for — of
+ * {@link ConnectedAppBroker.authProviderOf}, which both providers implement. A resolver taken as
+ * a parameter would be a question every caller had to answer identically or see a different
+ * sibling set; with the store calling its own broker there is ONE answer by construction and
+ * nothing to keep in step. A deployment with no broker answers `[]` — a presence test on a
+ * declared value, and correct, because such a deployment holds no brokered rows at all.
+ *
+ * THE SCAN IS PRE-FILTERED ON THE ASKING IDENTITY, IN THE `WHERE`. Resolving the auth provider
+ * is a vendor call per app, on a screen somebody is waiting on; resolving it for every brokered
+ * row of the whole deployment and only then discarding the rows on other identities is unbounded
+ * in the one dimension that grows. `vendor_user_id` is a column, so the filter is free — and
+ * semantically identical, because {@link connectionSiblings} discards exactly those rows.
+ *
+ * An app the broker cannot answer for (`null`) is left out rather than guessed at: claiming a
+ * sibling that is not one is the failure D26 exists to prevent. If the asking app itself is
+ * unanswered the list is empty.
+ *
+ * `unanswered` IS HOW MANY OTHER APPS ON THE ASKING IDENTITY WERE LEFT OUT FOR THAT REASON — an
+ * integer, never names; see {@link resolveSiblings} for why it is surfaced.
+ *
+ * IT ANSWERS A FACT AND ACTS ON NOTHING: no revocation, no deletion, no audit write. Returns the
+ * sibling toolkits, sorted, never including `toolkit`, and the unanswered count.
+ */
+ async siblingApps(input: {
+ toolkit: string;
+ account: AccountRef;
+ }): Promise<{ siblings: string[]; unanswered: number }> {
+ return resolveSiblings(input);
+ },
+
+ /**
+ * Start an Arcade authorization attempt for one person: take the key if it is free, expired or
+ * completed, and refuse if a LIVE attempt holds it. ONE STATEMENT.
+ *
+ * `actorId` IS THE SESSION SUBJECT (`users.id`), NEVER `credentialActorId`. This file reads
+ * `input.credentialActorId ?? input.actorId`, and on a Team Bot those differ, and neither is the
+ * person holding the browser. The verifier finds the attempt from the returning browser's
+ * session, so any other key writes attempts a browser can never match.
+ *
+ * ONE STATEMENT BECAUSE A READ-THEN-WRITE LETS TWO REQUESTS BOTH SEE "NO LIVE ATTEMPT" AND BOTH
+ * INSERT. The conditional `ON CONFLICT ... DO UPDATE ... WHERE` is what makes the claim atomic:
+ * the second request waits on the first's row and then evaluates the WHERE against it. The
+ * live row is answered by the same statement, in a branch that only produces rows when the
+ * claim produced none. It can be null: a rival that committed after this statement took its
+ * snapshot is live but not yet visible to it, and the caller's 409 then names nothing.
+ *
+ * `created_at` is truncated to milliseconds so the `Date` handed back names the stored value
+ * exactly — a microsecond no `Date` can carry would otherwise sit in the row unreachable.
+ * {@link recordAuthorizationMint} and {@link discardAuthorizationAttempt} do not RELY on that:
+ * they truncate the column in the comparison too, so a row written by anything else — the
+ * column's own `defaultNow()`, say — is still reachable. See {@link attemptNamed}.
+ */
+ async claimAuthorizationAttempt(input: {
+ actorId: string;
+ serverId: string;
+ action: string;
+ account: AccountRef;
+ }): Promise<
+ | { claimed: true; attempt: PendingAuthorization }
+ | { claimed: false; live: PendingAuthorization | null }
+ > {
+ const rows = await database.execute(sql`
+ WITH claimed AS (
+ INSERT INTO pending_authorizations
+ (actor_id, server_id, action, holder, user_id, vendor_user_id, created_at)
+ VALUES (
+ ${input.actorId}, ${input.serverId}, ${input.action}, ${input.account.holder},
+ ${input.account.holder === "person" ? input.account.userId : null},
+ ${input.account.vendorUserId}, date_trunc('milliseconds', now())
+ )
+ ON CONFLICT (actor_id) DO UPDATE SET
+ server_id = EXCLUDED.server_id,
+ action = EXCLUDED.action,
+ holder = EXCLUDED.holder,
+ user_id = EXCLUDED.user_id,
+ vendor_user_id = EXCLUDED.vendor_user_id,
+ auth_id = NULL,
+ created_at = EXCLUDED.created_at,
+ completed_at = NULL,
+ outcome = NULL
+ WHERE pending_authorizations.created_at < now() - interval '15 minutes'
+ OR pending_authorizations.completed_at IS NOT NULL
+ RETURNING *
+ )
+ SELECT true AS claimed, claimed.* FROM claimed
+ UNION ALL
+ SELECT false AS claimed, p.* FROM pending_authorizations p
+ WHERE p.actor_id = ${input.actorId}
+ AND NOT EXISTS (SELECT 1 FROM claimed)
+ `);
+ const row = rows[0] as Record | undefined;
+ if (!row) return { claimed: false, live: null };
+ const attempt = pendingAuthorizationFromRow(row);
+ return row.claimed === true
+ ? { claimed: true, attempt }
+ : { claimed: false, live: attempt };
+ },
+
+ /**
+ * Write the `ar_...` the vendor minted onto the attempt, matched on `(actor_id, created_at)`
+ * through {@link attemptNamed}, which is where the precision of that match is argued.
+ *
+ * WRITE-ONCE, AND NEVER AFTER SETTLEMENT: the UPDATE also requires `auth_id IS NULL` and
+ * `completed_at IS NULL`. `auth_id` is what binds the attempt to the authorization the person
+ * was actually sent to consent to, and both conditions are needed to keep that binding fixed.
+ *
+ * `completed_at IS NULL` is {@link closeAuthorizationAttempt}'s rule, for the same reason: a
+ * settled attempt is a record, not a draft. Without it, a late or duplicated mint rewrote a
+ * completed attempt's `ar_...`, even to NULL, and the row no longer named the authorization
+ * the person consented to.
+ *
+ * `auth_id IS NULL` is needed because `completed_at IS NULL` alone still lets a duplicated mint
+ * rebind a LIVE attempt. Once the person has the first id's consent URL, writing a second id
+ * (or NULL) makes their return leg match nothing. A NULL `auth_id` is never valid after
+ * settlement: every settling write (`completeAuthorizationAttempt` and the three verdicts
+ * through `closeAuthorizationAttempt`) matches `auth_id = $1`, which NULL never satisfies.
+ * So `auth_id IS NULL` alone would already rule out any row the store settled. `completed_at
+ * IS NULL` is kept anyway, so the guard does not depend on every future writer settling
+ * through those paths.
+ *
+ * The first write is the only one, and it can be NULL: a vendor that answered with no id
+ * leaves the attempt null, and it expires. Answers whether it wrote. False means the attempt
+ * was discarded, replaced, already minted or already settled. The authorize route acts on it:
+ * on false it does not hand out the consent url, because nothing here records that flow.
+ */
+ async recordAuthorizationMint(input: {
+ actorId: string;
+ /** A `Date`, or the ISO string one becomes through JSON; see {@link attemptInstant}. */
+ createdAt: Date | string;
+ /** Null when the vendor answered with no id: the attempt then can never complete. */
+ authId: string | null;
+ }): Promise {
+ const written = await database
+ .update(pendingAuthorizations)
+ .set({ authId: input.authId })
+ .where(
+ and(
+ attemptNamed(input.actorId, input.createdAt),
+ isNull(pendingAuthorizations.authId),
+ isNull(pendingAuthorizations.completedAt),
+ ),
+ )
+ .returning({ actorId: pendingAuthorizations.actorId });
+ return written.length > 0;
+ },
+
+ /**
+ * Delete one attempt, matched on `(actor_id, created_at)` SO IT CAN NEVER DELETE A NEWER
+ * ATTEMPT BY THE SAME ACTOR. Matching on `auth_id` would be wrong: an attempt whose mint never
+ * completed has none, and that is exactly the one most worth cancelling.
+ *
+ * CONDITIONAL ON `completed_at IS NULL`, THE SAME RULE {@link closeAuthorizationAttempt} AND
+ * THE COMPLETION HOLD, BECAUSE A SETTLED ATTEMPT IS A RECORD AND NOT A DRAFT. `(actor_id,
+ * created_at)` does not stop naming a row when the row settles, and every caller of this reads
+ * the attempt FIRST and deletes it AFTER. Unconditional, a completion committing in that gap
+ * was erased outright — the one row saying this person's consent went through, with the grant
+ * live at the vendor, the `brokered_connections` row written and `mcp.account_connected` on
+ * the trail — and the cancel route then answered `{ cancelled: true }` for a consent that had
+ * succeeded. The DELETE waits on the completion's row lock and re-evaluates this condition
+ * against the committed row, so the guard holds under the race and not merely before it.
+ *
+ * `false` therefore means one of three things — no row under that key, a row under a different
+ * `created_at`, or a row that has settled — and all three are "nothing was discarded". A caller
+ * that must tell the third apart asks {@link settledAuthorizationOutcome}.
+ */
+ async discardAuthorizationAttempt(input: {
+ actorId: string;
+ /** A `Date`, or the ISO string one becomes through JSON; see {@link attemptInstant}. */
+ createdAt: Date | string;
+ }): Promise {
+ const deleted = await database
+ .delete(pendingAuthorizations)
+ .where(
+ and(
+ attemptNamed(input.actorId, input.createdAt),
+ isNull(pendingAuthorizations.completedAt),
+ ),
+ )
+ .returning({ actorId: pendingAuthorizations.actorId });
+ return deleted.length > 0;
+ },
+
+ /**
+ * How the attempt named by `(actor_id, created_at)` ended, or null while it has not ended, or
+ * when no row carries that key.
+ *
+ * FOR THE ONE CALLER THAT HAS TO SAY WHY A DISCARD REFUSED: the cancel route, which must not
+ * answer a person whose consent COMPLETED while they were cancelling it as though nothing had
+ * happened. Named through {@link attemptNamed} like the discard it explains, so it can only
+ * report the attempt that discard was aimed at and never a newer one by the same actor. Once
+ * settled, a row keeps that `created_at` until a new claim takes the key and rewrites it, so a
+ * reading after the refused DELETE answers for the same attempt the DELETE refused.
+ */
+ async settledAuthorizationOutcome(input: {
+ actorId: string;
+ createdAt: Date | string;
+ }): Promise {
+ const [row] = await database
+ .select({ outcome: pendingAuthorizations.outcome })
+ .from(pendingAuthorizations)
+ .where(attemptNamed(input.actorId, input.createdAt));
+ return row?.outcome ?? null;
+ },
+
+ /**
+ * Finish one Arcade authorization: consume the attempt, record the connection and file the
+ * trail, ALL THREE IN ONE TRANSACTION OR NONE OF THEM.
+ *
+ * THE ATTEMPT ROW IS THE SERIALIZATION POINT AND THE DATABASE ENFORCES IT. The first statement
+ * is a conditional `UPDATE ... WHERE actor_id = $1 AND auth_id = $2 AND completed_at IS NULL
+ * RETURNING`, and it is what makes completion happen exactly once. A read of the attempt
+ * followed by a write would not: two callbacks can both read the row before either consumes it,
+ * and both would then write a connection and a second `mcp.account_connected`. The second
+ * caller's UPDATE instead waits on the first's row lock, re-evaluates `completed_at IS NULL`
+ * against the committed row, and matches nothing.
+ *
+ * AN `auth_id` IN AN AUDIT PAYLOAD ENFORCES NOTHING, which is the shape this replaces.
+ * {@link auditEvents.id} is `uuid().defaultRandom()`, so the trail has no key a second callback
+ * could collide with — it would simply hold two rows for one connection, and the connection
+ * upsert would have run twice on the way there.
+ *
+ * ZERO ROWS MEANS STOP, AND THE CALLER IS TOLD NOTHING, which is deliberate. Zero rows is
+ * either somebody else completing this attempt first or a callback naming an attempt that is
+ * not this one, and NEITHER is a thing this deployment can tell apart or should act on
+ * differently: both wrote nothing and both leave the browser looking at an account that is
+ * connected. So this answers the same for the loser as for the winner, and a replayed callback
+ * is a no-op rather than an error somebody has to explain. A caller wanting to know what the
+ * row says reads {@link authorizationAttemptFor}.
+ *
+ * THE CONNECTION AND THE AUDIT ROW ARE IN THE SAME TRANSACTION AS THAT UPDATE and not merely
+ * after it. A failure between them used to leave the attempt consumed and nothing recording
+ * what it was consumed FOR: the person's account is live at the vendor, this deployment holds
+ * no `brokered_connections` row — which is the whole of the permission a brokered call is
+ * decided on — and the attempt can never be completed again. The audit insert is bound to the
+ * transaction for the same reason, through {@link createAuditStore} over the transaction handle
+ * rather than the store's own `auditStore`, which holds the pool and would commit on its own.
+ *
+ * `scopes` IS WHAT THE VENDOR GRANTED, AND IT IS THE ONLY THING FROM THAT ANSWER THAT IS
+ * WRITTEN ANYWHERE. Arcade's `auth.status` carries a live provider access token and an
+ * authorization URL beside it; neither reaches this row, this method's arguments, a log line or
+ * an error. What the trail gets is the action the attempt was for and the scopes that came
+ * back, which is exactly what is needed later to explain a tool call refused for want of a
+ * scope — the question {@link mcpUserCredentials.scope} exists to answer for a `user-oauth`
+ * grant. `authProvider` is the authorization provider this deployment ASKED for (`google`, say, as
+ * distinct from the broker, `brokered_connections.provider`). The caller resolves it from the
+ * app's own row at freshness-revalidation time and passes it in; the attempt row has no
+ * provider column to carry it. It is never read back out of a vendor reply, and the narrowed
+ * `status` return of D31 makes that structurally impossible.
+ */
+ async completeAuthorizationAttempt(input: {
+ actorId: string;
+ authId: string;
+ toolkit: string;
+ account: AccountRef;
+ action: string;
+ scopes: string[];
+ authProvider: string;
+ }): Promise {
+ await database.transaction(async (transaction) => {
+ /*
+ * ONE STATEMENT, AND THE FIRST ONE. `completed_at` is truncated to milliseconds for the
+ * reason `claimAuthorizationAttempt` truncates `created_at`: a `Date` carries only
+ * milliseconds, and a column this deployment writes at microsecond precision is one no
+ * later `Date`-valued filter can ever match. Nothing matches on this column today; writing
+ * it the same way is what keeps that true if something does.
+ */
+ const claimed = await transaction.execute(sql`
+ UPDATE pending_authorizations
+ SET completed_at = date_trunc('milliseconds', now()),
+ outcome = 'completed'
+ WHERE actor_id = ${input.actorId}
+ AND auth_id = ${input.authId}
+ AND completed_at IS NULL
+ RETURNING actor_id
+ `);
+ if (claimed.length === 0) return;
+
+ // Read inside the transaction, under the attempt's lock, so the answer the audit row
+ // carries is the one this very write acted on rather than one a rival moved underneath it.
+ const [existing] = await transaction
+ .select({ app: brokeredConnections.app })
+ .from(brokeredConnections)
+ .where(accountRow(input.toolkit, input.account));
+
+ const verifiedAt = new Date();
+ const set = {
+ verified: true,
+ verifiedAt,
+ // NOTHING WAS SPENT TO EARN THAT FLAG. A consent screen is the vendor's own yes, and no
+ // action of the app's is ever called against it; see `confirmBrokeredConnection`, which
+ // writes the same pair for the same reason.
+ probeAction: null,
+ updatedAt: verifiedAt,
+ };
+ /*
+ * WHO CONNECTED THE TEAM ACCOUNT, WRITTEN HERE FOR THE SAME REASON EVERY OTHER WRITER
+ * WRITES IT. `connectedBy` is only ever a deployment-held row's column — a person's row is
+ * attributed by `user_id` already — and for a Shared app it is the attribution an
+ * administrator reads off `deploymentConnectionFor` to decide whether a team connection is
+ * legitimate and who to ask about it. This writer held `actorId`, the session subject
+ * (`users.id`) the attempt is keyed on and the id the admin read joins `users` on, and
+ * recorded none of it: an Arcade-consented Shared app's row was permanently null, and the
+ * page then said nobody connected it. The null is a real state for rows written before
+ * this, which is why both readers keep answering `null` rather than inventing a name.
+ *
+ * AND THE RE-CONSENT SUPERSEDES, which is `recordBrokeredConnection`'s rule spelled the
+ * same way: a deployment account is shared, so the administrator who most recently
+ * completed a consent for it is the one the row credits. Unconditional here, unlike there,
+ * because this path always holds an actor — there is no caller that passes none.
+ */
+ const connectedBy =
+ input.account.holder === "deployment" ? input.actorId : null;
+ await transaction
+ .insert(brokeredConnections)
+ .values({
+ // The broker this row was actually minted at, which is this store's own — never a
+ // literal, for the reason `recordBrokeredConnection` sets out at length.
+ provider: brokerProvider,
+ app: input.toolkit,
+ holder: input.account.holder,
+ userId:
+ input.account.holder === "person" ? input.account.userId : null,
+ vendorUserId: input.account.vendorUserId,
+ connectedBy,
+ ...set,
+ })
+ .onConflictDoUpdate(
+ input.account.holder === "person"
+ ? {
+ target: [
+ brokeredConnections.provider,
+ brokeredConnections.app,
+ brokeredConnections.userId,
+ ],
+ targetWhere: sql`${brokeredConnections.holder} = 'person'`,
+ // The identity the grant was just made under, for `recordBrokeredConnection`'s
+ // reason: calls go out under the row's id, so a re-consent that left the old one
+ // standing sent them to an account this consent says nothing about.
+ set: { ...set, vendorUserId: input.account.vendorUserId },
+ }
+ : {
+ target: [
+ brokeredConnections.provider,
+ brokeredConnections.app,
+ ],
+ targetWhere: sql`${brokeredConnections.holder} = 'deployment'`,
+ set: {
+ ...set,
+ vendorUserId: input.account.vendorUserId,
+ connectedBy,
+ },
+ },
+ );
+
+ await recordAuditEvent(
+ createAuditStore(transaction as unknown as Database),
+ {
+ eventType: "mcp.account_connected",
+ targetType: "mcp_server",
+ // The app, the id every other writer of this event files a brokered connection under,
+ // so one query answers what happened to one person's access to one app however it
+ // began and however it ended.
+ targetId: input.toolkit,
+ payload: {
+ actor: input.actorId,
+ server: input.toolkit,
+ /*
+ * WHAT THE VENDOR GRANTED, WHICH A BROKERED ROW HAS NEVER BEEN ABLE TO SAY BEFORE.
+ * Both Composio writers carry `""` because Composio holds the grant and never tells
+ * this deployment what it covers; Arcade does tell us, on the key all four writers
+ * already agree on, so the same query reads both.
+ */
+ scope: input.scopes.join(" "),
+ // The action the attempt was for: what this grant was obtained in order to call.
+ action: input.action,
+ authProvider: input.authProvider,
+ reconnected: Boolean(existing),
+ holder: input.account.holder,
+ },
+ },
+ );
+ });
+ },
+
+ /**
+ * Close an attempt as `failed` or `unreconciled`, matched on `(actor_id, auth_id)`.
+ *
+ * ON THE PAIR AND NOT ON `created_at`, unlike {@link recordAuthorizationMint} and
+ * {@link discardAuthorizationAttempt}, because the caller here is the returning callback and
+ * all it holds is the `ar_...`. An attempt with no `auth_id` matches nothing, which is correct:
+ * no callback can be about an attempt that never minted one.
+ *
+ * CONDITIONAL ON `completed_at IS NULL`, exactly as the completion above is: an attempt that
+ * has settled — completed, failed, unreconciled or withdrawn — is not reopened by a later
+ * callback, and `false` says so. See {@link closeAuthorizationAttempt} for what the asymmetry
+ * used to be and what it cost.
+ */
+ async failAuthorizationAttempt(input: {
+ actorId: string;
+ authId: string;
+ }): Promise {
+ return await closeAuthorizationAttempt(input, "failed");
+ },
+
+ /**
+ * Mark an attempt whose outcome this deployment could not settle — the vendor answered, and
+ * what it answered could not be reconciled with the attempt. Kept apart from `failed` so an
+ * operator can tell "the person declined" from "we do not know what happened".
+ */
+ async markAuthorizationUnreconciled(input: {
+ actorId: string;
+ authId: string;
+ }): Promise {
+ return await closeAuthorizationAttempt(input, "unreconciled");
+ },
+
+ /**
+ * Mark an attempt whose consent completed at the vendor and which this deployment then refused
+ * and ended by withdrawing the grant — neither a decline nor an unknown. An operator seeing a
+ * run of these is seeing apps whose mode is being switched inside a consent window, which is a
+ * different problem from people declining.
+ */
+ async markAuthorizationWithdrawn(input: {
+ actorId: string;
+ authId: string;
+ }): Promise {
+ return await closeAuthorizationAttempt(input, "withdrawn");
+ },
+
+ /**
+ * The live attempt for a session subject (`users.id`, never `credentialActorId`), or null when
+ * there is none, it is past the 15-minute TTL, or it has completed.
+ */
+ /**
+ * Whether this attempt's completion COMMITTED — asked of the row the transaction serializes on.
+ *
+ * FOR ONE CALLER AND ONE QUESTION: `/arcade/verify`'s completion rejected, and "the call threw"
+ * is not "the work rolled back". A connection lost on the way back from a COMMIT the server had
+ * already applied rejects at the client over a grant that is written down, and the verifier
+ * telling that person `not-recorded` sends them to mint a second consent at the vendor against
+ * a connection they already hold. The attempt row is the one place that can say which happened:
+ * {@link createPluginStore}'s `completeAuthorizationAttempt` stamps `outcome = 'completed'` in
+ * the first statement of the transaction, so the stamp exists exactly when the transaction did.
+ *
+ * NOT {@link authorizationAttemptFor}, which cannot answer this. It filters on `completed_at IS
+ * NULL` AND on the fifteen-minute TTL, so it answers `null` for a completed attempt and for an
+ * expired one alike — and a verifier that read "no live attempt" as "it committed" would
+ * report a grant that is not there for any completion that ran long enough to age out.
+ *
+ * MATCHED ON `(actor_id, auth_id)` like the closers above and never on `created_at`, because
+ * what the caller holds at this point is the `ar_...`. A rival callback having completed the
+ * same attempt first answers `true` here, which is correct: the grant is recorded either way,
+ * and this deployment has never been able or willing to tell the winner from the loser.
+ */
+ async authorizationAttemptCompleted(input: {
+ actorId: string;
+ authId: string;
+ }): Promise {
+ const [row] = await database
+ .select({ actorId: pendingAuthorizations.actorId })
+ .from(pendingAuthorizations)
+ .where(
+ and(
+ eq(pendingAuthorizations.actorId, input.actorId),
+ eq(pendingAuthorizations.authId, input.authId),
+ eq(pendingAuthorizations.outcome, "completed"),
+ ),
+ );
+ return row !== undefined;
+ },
+
+ async authorizationAttemptFor(
+ actorId: string,
+ ): Promise {
+ const [row] = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(
+ and(
+ eq(pendingAuthorizations.actorId, actorId),
+ isNull(pendingAuthorizations.completedAt),
+ sql`${pendingAuthorizations.createdAt} >= now() - interval '15 minutes'`,
+ ),
+ );
+ return row ?? null;
+ },
+
+ /**
+ * File `mcp.authorization_cancelled` for an attempt its own person has just retracted.
+ *
+ * CALLED ONLY AFTER {@link discardAuthorizationAttempt} ANSWERED TRUE, by the cancel route and by
+ * nothing else. The same deletion also tidies up after a refused or unrecorded mint, and those
+ * are this deployment cleaning up after itself, not a person withdrawing consent — so the row is
+ * filed here, for the one caller whose act it describes, and not inside the deletion.
+ *
+ * TARGETED AT THE APP'S SLUG, read off the attempt's server row with {@link toolkitOfFor}, which
+ * is the id `mcp.account_connected` and `mcp.account_disconnected` file a brokered app under, so
+ * one query on the target answers every ending of one app's consent. An attempt OUTLIVES what
+ * its row's url says — an administrator can re-point the row while a consent is in flight, and
+ * the cancel route deliberately still lets the person retract it — and once the url names no
+ * app the row's own id is the only name left for what was cancelled, so that is what the row is
+ * filed under; `serverId` is in the payload either way.
+ *
+ * WHO DID IT GOES IN THE PAYLOAD, as on every writer here, never in `actorUserId`. And the
+ * payload carries NEITHER the consent url NOR the attempt's `auth_id`: the attempt row never held
+ * the url, and the vendor's handle is not needed to say what a person withdrew. `minted` is
+ * whether a flow had been started at the vendor at all.
+ */
+ async recordAuthorizationCancelled(input: {
+ actorId: string;
+ attempt: PendingAuthorization;
+ }): Promise {
+ const [row] = await database
+ .select({ url: mcpServers.url })
+ .from(mcpServers)
+ .where(eq(mcpServers.id, input.attempt.serverId))
+ .limit(1);
+ const toolkit = row ? toolkitOfFor(brokerProvider, row.url) : null;
+ await recordAuditEvent(auditStore, {
+ eventType: "mcp.authorization_cancelled",
+ targetType: "mcp_server",
+ targetId: toolkit ?? input.attempt.serverId,
+ payload: {
+ actor: input.actorId,
+ server: toolkit,
+ serverId: input.attempt.serverId,
+ action: input.attempt.action,
+ holder: input.attempt.holder,
+ minted: input.attempt.authId !== null,
+ },
+ });
+ },
+
/**
* Spend one call on this person's key, and say what the vendor made of it.
*
@@ -1012,9 +1626,42 @@ export function connectionMethods(context: StoreContext) {
* Composio transport strips before anything reaches the vendor and asserts that it did, so
* what Composio is handed is the action and an empty argument object.
*/
+ /*
+ * AND ON ANY OTHER BROKER THIS REFUSES RATHER THAN DIALS, because `composioAskAction` is
+ * Composio's own `askAction` and no counterpart has been written for a second broker.
+ *
+ * CRITERION. A deployment whose broker is not Composio gets a stated refusal here, and no
+ * request leaves this process.
+ *
+ * REASON. The url this used to compose was `composio://` whatever the broker was, so
+ * an Arcade deployment's check was sent to Composio with a Composio-shaped url. On a
+ * deployment holding no Composio key that fails at a vendor it has no business talking to and
+ * the person is told their connection could not be checked — an outage reported where there
+ * was none. Where a Composio key IS present it is worse: the slug is a plain app name, so the
+ * call lands on an UNRELATED app of the same name at the wrong vendor and succeeds, and the
+ * row records a verified check of an account nobody holds.
+ *
+ * `unreachable` IS THE HONEST OUTCOME, not `nothing`. Nothing was learned about the key and
+ * nothing is recorded about it — `probe` is null, so {@link _ProbeOutcomeLeaves} leaves the
+ * row untouched and `probe_action` unwritten — and `attempted` is what WOULD have been spent,
+ * which is the fact about this deployment's intent that field exists to carry. The sentence
+ * names the state rather than blaming the connection, because the gap is ours.
+ *
+ * TODO: delete this once a probe is designed for the second broker. Until then a refusal that
+ * says so is the only answer that is not a claim about somebody's account.
+ */
+ if (brokerProvider !== "composio") {
+ return {
+ outcome: "unreachable",
+ probe: null,
+ attempted: candidate.name,
+ sentence: `This deployment brokers its apps through ${BROKERED_PROFILE[brokerProvider].vendor}, and the only connection check this deployment knows how to make is Composio's. Nothing was asked about this connection, so nothing is recorded about it.`,
+ };
+ }
+
const { result, answered } = await composioAskAction(
{
- url: `composio://${input.toolkit}`,
+ url: `${BROKERED_PROFILE.composio.scheme}${input.toolkit}`,
actorId: input.account.vendorUserId,
// Spread rather than passed as `undefined`, so "any account of theirs" reaches the wire
// as a body with no such key. See {@link ComposioActions.execute}.
@@ -1139,7 +1786,7 @@ export function connectionMethods(context: StoreContext) {
}): Promise<{ connected: boolean }> {
// Before anything, and for the reason `addBrokeredApp` says it first too: a deployment with
// no key has no broker to have connected anybody at, so there is nothing here to ask.
- if (!broker) throw new BrokerUnconfiguredError();
+ if (!broker) throw new BrokerUnconfiguredError(brokerProvider, "person");
const connected = await broker.isConnected({
account: input.account,
@@ -1453,7 +2100,7 @@ export function connectionMethods(context: StoreContext) {
}): Promise<{ connected: true; verified: boolean; probe: string | null }> {
// First, and for `confirmBrokeredConnection`'s reason: a deployment with no key has nobody to
// connect anybody at, and the refusal must happen before the values are touched at all.
- if (!broker) throw new BrokerUnconfiguredError();
+ if (!broker) throw new BrokerUnconfiguredError(brokerProvider, "person");
// Keyed on the url and on the one row that answers for it — see `brokeredAppScheme`. It is
// the same reasoning the connection gate in `connectionTokenFor` is keyed on, and for the
@@ -1855,7 +2502,7 @@ export function connectionMethods(context: StoreContext) {
verifiedAt: string | null;
probe: string | null;
}> {
- if (!broker) throw new BrokerUnconfiguredError();
+ if (!broker) throw new BrokerUnconfiguredError(brokerProvider, "person");
// Keyed on the url and on the one row that answers for it — see `brokeredAppKind`. It is the
// read `connectBrokeredWithFields`, `confirmBrokeredConnection` and `disconnectBrokered` all
@@ -1942,7 +2589,7 @@ export function connectionMethods(context: StoreContext) {
*/
if (probed.outcome === "unreachable") {
throw new PluginRefusedError(
- `${input.toolkit} could not be checked just now: ${probed.sentence} Nothing here changed — your connection is still recorded exactly as the last check left it — so pressing Re-check again when Composio is answering is the whole of the retry.`,
+ `${input.toolkit} could not be checked just now: ${probed.sentence} Nothing here changed — your connection is still recorded exactly as the last check left it — so pressing Re-check again when ${BROKERED_PROFILE[brokerProvider].vendor} is answering is the whole of the retry.`,
null,
);
}
@@ -2162,7 +2809,7 @@ export function connectionMethods(context: StoreContext) {
*/
reason: "self" | "person_removed" | "admin" | "mode_switched";
}): Promise<{ vendorRevocationRequested: boolean }> {
- if (!broker) throw new BrokerUnconfiguredError();
+ if (!broker) throw new BrokerUnconfiguredError(brokerProvider, "person");
/*
* Keyed on the url and on the one row that answers for it — see {@link brokeredAppKind}.
@@ -2179,7 +2826,10 @@ export function connectionMethods(context: StoreContext) {
* and an app it cannot say holds a key is one whose withdrawal it has to report as asked.
*/
/**
- * WHAT THIS DISCONNECT CLAIMS ON THE TRAIL FOR EACH KIND OF APP.
+ * WHAT THIS DISCONNECT CLAIMS ON THE TRAIL FOR THE ASKED APP, BY KIND. What it does about a
+ * SIBLING of each kind is a different decision and is written down where it is made — see
+ * rule 1 on {@link clearEndedSiblings}, which for a key DELETES NOTHING AND CLAIMS NOTHING
+ * rather than claiming no revocation about a row it removed.
*
* Type-only and erased; see {@link Decides}. `=== "key"` collapses three answers into two,
* and the collapse is deliberate here rather than accidental: the field below is a claim that
@@ -2196,20 +2846,64 @@ export function connectionMethods(context: StoreContext) {
unreadable: "reports it as asked for too, which is the claim that cannot be too weak";
}
>;
- const fieldScheme = (await brokeredAppKind(input.toolkit)) === "key";
+ /*
+ * THE APPS THIS REVOCATION ENDS BESIDES THE ONE ASKED ABOUT, read BEFORE anything is revoked.
+ * Where a connection is keyed `(identity, auth provider)` — Arcade — withdrawing one app's
+ * connection ends every app that shares it, so a local row left for one of them claims an
+ * account the vendor no longer holds, which is the other way to lie. Where each app is its
+ * own auth provider — Composio — this is always empty and nothing below differs from a
+ * disconnect of one. The question is {@link resolveSiblings}' and none of it is derived here.
+ */
+ const { siblings, unanswered } = await resolveSiblings({
+ toolkit: input.toolkit,
+ account: input.account,
+ });
+
+ /*
+ * WHETHER THE ASKED APP IS ONE THIS DEPLOYMENT HOLDS A KEY FOR, which decides WHAT THE TRAIL
+ * CLAIMS about it and nothing else. Its row goes either way: the person pressed Disconnect on
+ * THIS app, the row is the whole of what Disconnect works off, and leaving it would be
+ * refusing the act. The siblings are the opposite question — nobody asked about them — and
+ * {@link clearEndedSiblings} answers it, rule 1 included.
+ *
+ * This was one `keyApps` set spanning the asked app and its siblings, and the set only ever
+ * weakened the trail claim: a KEY sibling's row was deleted anyway, by the single OAuth
+ * revoke that never ended it, leaving a live account at the vendor with no local row to
+ * reach it.
+ *
+ * The claim is {@link vendorRevocationClaim}'s, which {@link removeServer} and
+ * {@link retireConnectionsFor} make for their own asked apps: this method was the one of the
+ * three that said `false` for a key, and now none of them says it for itself.
+ */
+ const claim = await vendorRevocationClaim(input.toolkit);
// Whether there was an account to end at all, which is what decides if anybody was
// disconnected. Named apart from the field below because for a key the two differ: something
// ended, and nothing was asked of the provider.
+ //
+ // REVOKE BEFORE ANY LOCAL DELETE, and ONE ask: it ends the siblings with it. It THROWS on a
+ // partial ask, which is the partial failure — the throw leaves this method before a single
+ // row is deleted or written, every app's row standing, so pressing Disconnect again is the
+ // recovery.
const ended = await broker.revoke({
account: input.account,
toolkit: input.toolkit,
});
- const vendorRevocationRequested = fieldScheme ? false : ended;
+ const asked = claim(ended);
- // `returning` because whether a row was here is half of what decides if anybody was
- // disconnected, and a delete that answered nothing would leave the two cases indistinguishable.
+ /*
+ * THE ASKED APP FIRST, and on its own terms — this is the act, not a cascade of it.
+ *
+ * `deleted || ended` RATHER THAN RULE 3, deliberately and only here: a disconnect of an app
+ * whose row this deployment never held is still a withdrawal the vendor made, and the trail
+ * has to carry it. A SIBLING has no such act of its own to record, which is why
+ * {@link clearEndedSiblings} files nothing without a row.
+ *
+ * `returning` because whether a row was here is half of what decides if anybody was
+ * disconnected, and a delete that answered nothing would leave the two cases
+ * indistinguishable.
+ */
const [deleted] = await database
.delete(brokeredConnections)
.where(accountRow(input.toolkit, input.account))
@@ -2225,19 +2919,154 @@ export function connectionMethods(context: StoreContext) {
server: input.toolkit,
// Whose account this was, which is not always who ended it: an administrator
// offboarding somebody and a person disconnecting themselves write the same shape of
- // row, and only these two fields tell them apart. The deployment's own account has no
- // person behind it, so it is named as the deployment rather than as whoever pressed.
+ // row, and only these two fields tell them apart. The deployment's own account has
+ // no person behind it, so it is named as the deployment rather than as whoever
+ // pressed.
owner:
input.account.holder === "person"
? input.account.userId
: "deployment",
reason: input.reason,
- vendorRevocationRequested,
+ ...asked,
+ /*
+ * HOW MANY apps on this identity could not be asked about and so were left out of
+ * the set below, never their names. An integer, so an operator can see that "we did
+ * not know" happened; see {@link resolveSiblings}.
+ */
+ unansweredSiblings: unanswered,
},
});
}
- return { vendorRevocationRequested };
+ // Then the apps that went with it, each naming the disconnect that ended it, by the three
+ // rules that are written down once; see {@link clearEndedSiblings}.
+ await clearEndedSiblings({
+ siblings,
+ account: input.account,
+ endedWith: input.toolkit,
+ revoked: ended,
+ unanswered,
+ by: input.by,
+ reason: input.reason,
+ });
+
+ return asked;
+ },
+
+ /**
+ * THE LOCAL SIDE OF A WITHDRAWAL SOMEBODY ELSE ALREADY MADE AT THE VENDOR: the rows this
+ * deployment holds for an account a revoke has just ended, and the trail that says why they
+ * went.
+ *
+ * WHO THIS IS FOR. The verifier refuses a consent whose context moved on and withdraws the
+ * RETIRED identity's app at the vendor — a revoke it issues itself, outside this store,
+ * because the decision is the route's and the attempt row is what records it. On Arcade that
+ * one revoke ends the whole `(identity, auth provider)` connection and so every app sharing
+ * it, and the rows here for those apps would otherwise be left standing: this deployment's own
+ * records claiming live connections for apps the vendor has already disconnected. A person
+ * then sees apps listed as connected that fail on first use, and nothing tells them otherwise.
+ * This is the write that stops that, and it is a STORE method rather than route code for the
+ * reason every other row write is: `brokered_connections` and the trail are this file's.
+ *
+ * IT ASKS NOTHING OF ANY VENDOR AND DECIDES NOTHING BUT WHAT A LOCAL ROW SAYS. The caller
+ * resolved the siblings — of
+ * {@link siblingApps}, BEFORE its revoke went out, which is the ordering {@link
+ * disconnectBrokered} and {@link removeServer} both keep — and the caller watched its revoke
+ * ANSWER TRUE. Called on anything less, it would be claiming an account gone on the strength
+ * of an answer nobody got; that rule is {@link removeServer}'s and this one is its other half.
+ *
+ * ONLY A ROW THAT WAS ACTUALLY HERE GETS A TRAIL ROW, which is again {@link removeServer}'s
+ * rule. The act itself is already recorded — on the attempt row, as `withdrawn` — so these
+ * rows exist to explain why an app's connection VANISHED from this deployment's records, and
+ * an app that had no row here had nothing to explain.
+ *
+ * THAT RULE AND THE TWO BESIDE IT NOW LIVE IN {@link clearEndedSiblings}, which this method
+ * hands its siblings to and which {@link disconnectBrokered} and {@link removeServer} hand
+ * their own to. They were spelled three times and the spellings disagreed about every one of
+ * them.
+ *
+ * `reason` IS ONE WORD AND IT IS THIS ONE. "The consent's context moved on" is not any of the
+ * four {@link disconnectBrokered} takes — nobody disconnected anything, nobody was offboarded,
+ * and a mode switch is only one of the several ways the context can move — so the trail gets
+ * its own word rather than the nearest-looking lie. Fixed in the type so there is no second
+ * caller spelling it differently.
+ */
+ async clearWithdrawnAccount(input: {
+ toolkit: string;
+ account: AccountRef;
+ /** The apps the caller's revoke ended besides `toolkit`; {@link siblingApps} answers it. */
+ siblings: string[];
+ /**
+ * How many apps on this identity could not be asked about, an integer and never names, which
+ * qualifies the `endedWith` claim and goes beside it and nowhere else; see
+ * {@link resolveSiblings}.
+ */
+ unanswered: number;
+ by: string;
+ reason: "context_changed";
+ }): Promise<{ cleared: string[] }> {
+ const cleared: string[] = [];
+ /*
+ * THE ASKED APP FIRST, then the apps that went with it, so the trail reads in the order the
+ * withdrawal happened. The asked app's own row names no other app beside it — it was ended
+ * by its own ask — and a sibling's names the app that asked, which is the only thing that
+ * tells a reader why a connection they never touched is gone.
+ *
+ * THE ASKED APP TAKES THE SAME THREE RULES, which is this method's whole difference from
+ * {@link disconnectBrokered}'s asked app and is right for the same reason the rules are:
+ * nobody here pressed anything about this app either. A caller's revoke ended an OAuth
+ * connection for a RETIRED identity, so an app this deployment holds a key for was not in
+ * it (rule 1); the caller watched that revoke answer true, which is the contract above and
+ * is rule 2 discharged at the door; and a row that was not here had nothing to explain
+ * (rule 3). The act itself is already recorded on the attempt row, as `withdrawn`.
+ *
+ * Rule 1 is asked of {@link consentRevokeReaches}, its one spelling, rather than of the kind
+ * directly — this was the one site that compared the kind for itself (round 2, K5).
+ */
+ if (await consentRevokeReaches(input.toolkit)) {
+ const [deleted] = await database
+ .delete(brokeredConnections)
+ .where(accountRow(input.toolkit, input.account))
+ .returning({ app: brokeredConnections.app });
+ if (deleted) {
+ cleared.push(input.toolkit);
+ await recordAuditEvent(auditStore, {
+ eventType: "mcp.account_disconnected",
+ targetType: "mcp_server",
+ targetId: input.toolkit,
+ payload: {
+ actor: input.by,
+ server: input.toolkit,
+ owner:
+ input.account.holder === "person"
+ ? input.account.userId
+ : "deployment",
+ reason: input.reason,
+ /*
+ * TRUE FOR EVERY ROW HERE, AND IT IS NOT A GUESS: this method is reached only from a
+ * revoke that answered true, and on Arcade that ask ended the siblings with the app
+ * it named. `vendorRevocationRequested: false` means "no vendor was asked", which is
+ * the one thing none of these rows may say.
+ */
+ vendorRevocationRequested: true,
+ },
+ });
+ }
+ }
+
+ cleared.push(
+ ...(await clearEndedSiblings({
+ siblings: input.siblings,
+ account: input.account,
+ endedWith: input.toolkit,
+ // The contract above: the caller watched its revoke answer true before calling this.
+ revoked: true,
+ unanswered: input.unanswered,
+ by: input.by,
+ reason: input.reason,
+ })),
+ );
+ return { cleared };
},
/**
@@ -2367,6 +3196,16 @@ export function connectionMethods(context: StoreContext) {
* retirements of the same person revoke in the same order and write their rows in the same
* order.
*/
+ /*
+ * NO PROVIDER PREDICATE HERE, DELIBERATELY, AND THAT IS NOT AN OVERSIGHT. The sweep below
+ * revokes through the ACTIVE broker, so a row filed under another provider would be a
+ * revocation requested at the wrong vendor, which would not recognise the connection, and an
+ * audit row claiming `vendorRevocationRequested` about access that was never removed. What
+ * keeps that unreachable is the startup activation refusal (`assertActivatable`, called from
+ * `server/src/index.ts`): a deployment holding both providers' rows cannot boot. The provider
+ * sunset flow relaxes that refusal, and whoever builds it must add the `brokerProvider`
+ * predicate here.
+ */
const brokered = await database
.select({ toolkit: brokeredConnections.app })
.from(brokeredConnections)
@@ -2378,6 +3217,66 @@ export function connectionMethods(context: StoreContext) {
)
.orderBy(asc(brokeredConnections.app));
+ /*
+ * THE APPS EACH OF THIS PERSON'S REVOCATIONS ENDS BESIDES THE ONE ASKED ABOUT, read BEFORE a
+ * single revoke goes out and through {@link resolveSiblings}, the one place that answers it.
+ *
+ * WHY THIS LOOP NEEDS IT AT ALL, when it already asks about every app the person holds.
+ * Where a connection is keyed `(identity, auth provider)` — Arcade — the FIRST app's revoke
+ * ends the connection and every app sharing it, so the ask for a sibling that follows finds
+ * nothing left and answers `false`. Passed through, that is `vendorRevocationRequested:
+ * false` on the trail for a grant this deployment asked for and got, which is the one
+ * reading of that field that must never be written: it says "no vendor was asked" about an
+ * account that was withdrawn, to whoever later checks that an offboarding completed. The
+ * order the rows come back in decides which app gets the truthful row and which gets the
+ * false one, which is to say nothing decides it.
+ *
+ * Where each app is its own auth provider — Composio — every set here is empty by
+ * construction, each ask stands alone, and nothing below differs from what this method
+ * always did.
+ *
+ * RESOLVED AGAINST THE PERSON'S OWN IDENTITY, the same `account` the revokes below are asked
+ * with. MEMOIZED ACROSS THE WHOLE OFFBOARDING for the reason {@link removeServer} gives:
+ * `authProviderOf` is a live vendor call on Arcade, and the bound is then the number of
+ * DISTINCT TOOLKITS this person holds rather than that number squared. Empty with no broker
+ * configured, where nothing is asked of anybody and the `false` below is the whole truth.
+ */
+ const account = personAccount(userId);
+ const authProviders = new Map>();
+ const endedBesides = new Map<
+ string,
+ { siblings: string[]; unanswered: number }
+ >();
+ if (broker) {
+ for (const connection of brokered) {
+ endedBesides.set(
+ connection.toolkit,
+ await resolveSiblings(
+ { toolkit: connection.toolkit, account },
+ authProviders,
+ ),
+ );
+ }
+ }
+
+ /*
+ * WHAT THE TRAIL MAY CLAIM WAS ASKED OF THE VENDOR FOR EACH APP, read before a single revoke
+ * goes out, as {@link disconnectBrokered} reads its own. A KEY app's row says `false` whatever
+ * the vendor answered — see {@link vendorRevocationClaim}, which all three writers of an asked
+ * app's row now ask. This one used to pass the vendor's answer through for a key (round 2,
+ * L5-a), the same divergence {@link removeServer} had (A5c).
+ */
+ const toRetire: {
+ toolkit: string;
+ claim: Awaited>;
+ }[] = [];
+ for (const connection of brokered) {
+ toRetire.push({
+ toolkit: connection.toolkit,
+ claim: await vendorRevocationClaim(connection.toolkit),
+ });
+ }
+
/*
* What the broker was actually asked for each app, kept so the trail below records the answer
* rather than the call. False where there is no broker at all: a deployment whose key has
@@ -2397,21 +3296,29 @@ export function connectionMethods(context: StoreContext) {
* asked — a later one is not punished for an earlier one — and the first refusal is rethrown
* below, so the act still fails loudly and the administrator still gets a 500.
*/
- const withdrawn: { toolkit: string; requested: boolean }[] = [];
+ const withdrawn: {
+ toolkit: string;
+ requested: boolean;
+ claim: Awaited>;
+ }[] = [];
const refusals: unknown[] = [];
- for (const connection of brokered) {
+ for (const connection of toRetire) {
try {
withdrawn.push({
toolkit: connection.toolkit,
+ claim: connection.claim,
requested: broker
? await broker.revoke({
/*
* THE PERSON'S OWN ACCOUNT, NAMED AS ONE. Offboarding somebody ends what they
* held and never the deployment's shared account, whoever connected it — so the
- * holder is fixed here rather than read off anything, and the vendor id is the
- * person's own, which is what a personal account is filed under at Composio.
+ * holder is fixed above rather than read off anything, and the vendor id is the
+ * person's own, which is what a personal account is filed under at Composio. The
+ * SAME value the siblings were resolved against, because an answer about one
+ * identity describing a revocation on another is the whole of what that set is
+ * for.
*/
- account: { holder: "person", userId, vendorUserId: userId },
+ account,
toolkit: connection.toolkit,
})
: false,
@@ -2451,8 +3358,49 @@ export function connectionMethods(context: StoreContext) {
);
}
+ /*
+ * WHICH OF THIS PERSON'S APPS WERE ENDED BY ANOTHER APP'S REVOCATION, and by which one.
+ *
+ * Only from a revoke that ANSWERED TRUE: `false` means nothing was found to withdraw or the
+ * auth provider could not be resolved, and the vendor was not shown to have ended anything,
+ * so nothing is claimed on the strength of it — the rule {@link removeServer} files its
+ * siblings by. The asked app is skipped in favour of its own answer, and the first ender
+ * wins, which is deterministic because the rows are walked in app order.
+ *
+ * APPS THIS PERSON HOLDS ARE ALL THIS CAN SPEAK FOR. A sibling resolved on the same identity
+ * but held by the deployment, or an app whose own revoke threw and so has no row here, is
+ * left alone: this map only corrects what the loop below is already about to write.
+ *
+ * WHICH SIBLINGS A REVOKE ENDED IS {@link siblingsEndedBy}'S ANSWER, the one
+ * {@link clearEndedSiblings} deletes on — rules 1 and 2 — and not a walk of this method's
+ * own. It was one: rule 2 without rule 1, so a KEY app whose own ask answered false was filed
+ * as ended by an OAuth sibling's revoke that never reached it (round 2, A5b). What differs
+ * here is only what is DONE with the answer: this method deletes its own rows as its own acts
+ * above, so it takes the judgement without the delete and files `endedWith` from it.
+ */
+ const endedWith = new Map();
+ for (const entry of withdrawn) {
+ const ended = await siblingsEndedBy({
+ siblings: endedBesides.get(entry.toolkit)?.siblings ?? [],
+ endedWith: entry.toolkit,
+ revoked: entry.requested,
+ });
+ for (const app of ended) {
+ if (!endedWith.has(app)) endedWith.set(app, entry.toolkit);
+ }
+ }
+
for (const connection of withdrawn) {
retired += 1;
+ /*
+ * The app whose revocation ended this one, where this one's own ask came back empty
+ * because that revocation had already taken it. Absent for an app that was ended by its
+ * own ask, which needs no other grant named beside it.
+ */
+ const endedBy = connection.requested
+ ? undefined
+ : endedWith.get(connection.toolkit);
+ const resolved = endedBesides.get(connection.toolkit);
await recordAuditEvent(auditStore, {
eventType: "mcp.account_disconnected",
targetType: "mcp_server",
@@ -2473,8 +3421,30 @@ export function connectionMethods(context: StoreContext) {
* withdrawal asked for, or there was nothing to ask about, or there was no broker to
* ask. The value of the field is exactly that a reader can tell those apart, so a
* constant here would be worse than none.
+ *
+ * OR WHAT WAS ASKED ABOUT THE CONNECTION THIS APP SHARES, which is the same claim
+ * made about the same grant. On Arcade the ask that ended this app may have been
+ * spelled with a sibling's name, and `revoke`'s `false` here then means "already
+ * gone", not "nobody asked" — `endedWith` beside it names the app that asked.
+ *
+ * AND NEVER FOR A KEY: {@link vendorRevocationClaim} makes the claim from that
+ * reading, and for a key app it is `false` whatever the vendor answered.
+ */
+ ...connection.claim(connection.requested || Boolean(endedBy)),
+ /*
+ * AND WHERE A SIBLING RELATIONSHIP IS BEING CLAIMED, HOW MANY apps on this identity
+ * could not be asked about and so could not be ruled siblings either way — an
+ * integer, never their names; see {@link resolveSiblings}. Beside `endedWith` and
+ * nowhere else, which is where {@link removeServer} puts it and for its reason: the
+ * count qualifies a claim about which apps went together, and a row making no such
+ * claim would be carrying a number about nothing.
*/
- vendorRevocationRequested: connection.requested,
+ ...(endedBy
+ ? {
+ endedWith: endedBy,
+ unansweredSiblings: resolved?.unanswered ?? 0,
+ }
+ : {}),
},
});
}
diff --git a/server/src/plugins/plugin-store/context.ts b/server/src/plugins/plugin-store/context.ts
index 4d001db4f..62112a895 100644
--- a/server/src/plugins/plugin-store/context.ts
+++ b/server/src/plugins/plugin-store/context.ts
@@ -13,24 +13,34 @@ import {
mcpServers,
mcpTools,
mcpUserCredentials,
+ pendingAuthorizations,
pluginGrants,
skillTools,
} from "../../db/schema";
-import { accessFor, type ServerAccess } from "../access";
+import {
+ accessFor,
+ BROKERED_PROFILE,
+ type BrokerProvider,
+ type ServerAccess,
+ toolkitOfFor,
+} from "../access";
import { type Decides, type SchemeKind, schemeKind } from "../broker";
import { type CatalogueEntry, catalogueEntry } from "../catalogue";
-import { toolkitOf } from "../composio";
import { registerDynamicClient } from "../oauth";
import {
type AccountAnswer,
type AccountMode,
type AccountRef,
accountFor,
+ personAccount,
} from "../shared-accounts";
import {
+ type BrokeredDisconnectReason,
CatalogueEntryUnknownError,
CLIENT_REREGISTRATION_BACKOFF_MS,
+ type ConnectionFootprint,
CustomServerRefusedError,
+ connectionSiblings,
exchangeRefreshTokenOverHttp,
INVALID_CLIENT,
isUsableClient,
@@ -63,6 +73,12 @@ export function createStoreContext(options: PluginStoreOptions) {
// No default, unlike the seams above: there is no real implementation in this tree to fall back
// to, and a deployment with no Composio key is supposed to have no broker. See `./broker`.
const broker = options.broker;
+ /*
+ * The broker this store is for: every read of `brokered_connections`, every `provenance` test and
+ * the connection writer below read THIS rather than each re-deciding. Taken as given, never
+ * defaulted — see {@link PluginStoreOptions.brokerProvider} for why there is no fallback here.
+ */
+ const brokerProvider: BrokerProvider = options.brokerProvider;
/*
* One exchange at a time per (server, person). A rotating vendor invalidates the refresh
@@ -265,7 +281,9 @@ export function createStoreContext(options: PluginStoreOptions) {
* so only a removal racing this read gets here, and it is refused as the missing row is above.
*/
const toolkit =
- dialled.provenance === "composio" ? toolkitOf(dialled.url) : null;
+ dialled.provenance === brokerProvider
+ ? toolkitOfFor(brokerProvider, dialled.url)
+ : null;
const app = toolkit ? await brokeredAppRow(toolkit) : null;
if (toolkit && !app)
return {
@@ -299,8 +317,12 @@ export function createStoreContext(options: PluginStoreOptions) {
return {
refusal: `${row.title} runs in the account of the person asking, and this run is not attributed to anybody.`,
};
+ /*
+ * The actor's id is both the local id and the vendor id, deliberately and for both
+ * providers. See {@link personAccount} for why, before changing it.
+ */
return {
- ref: { holder: "person", userId: actorId, vendorUserId: actorId },
+ ref: personAccount(actorId),
title: row.title,
mode: "personal",
appId,
@@ -323,17 +345,69 @@ export function createStoreContext(options: PluginStoreOptions) {
const accountRow = (toolkit: string, account: AccountRef) =>
account.holder === "person"
? and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.app, toolkit),
eq(brokeredConnections.holder, "person"),
eq(brokeredConnections.userId, account.userId),
)
: and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.app, toolkit),
eq(brokeredConnections.holder, "deployment"),
);
+ /**
+ * The one statement behind `failAuthorizationAttempt`, `markAuthorizationUnreconciled` and
+ * `markAuthorizationWithdrawn`, so the three verdicts cannot drift into three row shapes.
+ * `completed_at` is truncated to milliseconds for the reason `claimAuthorizationAttempt`
+ * truncates `created_at`.
+ *
+ * CONDITIONAL ON `completed_at IS NULL`, EXACTLY AS THE COMPLETION ABOVE IS, BECAUSE A SETTLED
+ * ATTEMPT IS A RECORD AND NOT A DRAFT. Without that condition this UPDATE matched on
+ * `(actor_id, auth_id)` alone, and `(actor_id, auth_id)` does not stop naming a row when the row
+ * settles: a return leg that completed is still holding the `ar_...` a second leg will match on.
+ * So a slow or retried FAILURE callback arriving behind a SUCCESSFUL completion rewrote
+ * `outcome = 'completed'` as `outcome = 'failed'` and re-stamped `completed_at`, and the attempt
+ * then said the person's authorization failed while the grant was live at the vendor, the
+ * `brokered_connections` row was written and `mcp.account_connected` was on the trail. The trail
+ * and the attempt disagreed about what happened, and the attempt was the one that was wrong.
+ *
+ * THE OLD COMMENT HERE ARGUED THE ASYMMETRY WAS THE POINT, on the grounds that "a second one
+ * re-stamping the same verdict grants nothing to anybody". That is true of the case it imagined —
+ * `failed` over `failed` — and says nothing about the case that actually happens, which is a
+ * verdict over a DIFFERENT and already-final one. Completion is not the only write that must
+ * happen once; it is the only one that was treated that way.
+ *
+ * FIRST TERMINAL VERDICT WINS, AND A LATE ONE IS REFUSED RATHER THAN APPLIED. `false` now means
+ * one of three things — no attempt under that actor, an attempt holding a different `auth_id`,
+ * or an attempt that has already settled — and all three are "this call did not close anything",
+ * which is the only distinction a caller has ever acted on. Nothing is lost to the person on the
+ * losing leg: their own request still ends in the refusal redirect it would have ended in anyway.
+ * What this deployment cannot yet SAY is that two legs of one authorization disagreed; there is
+ * no audit event type for it and inventing one is wider than this change. Recording the true
+ * outcome is strictly better than recording the false one, which is what the alternative was.
+ */
+ const closeAuthorizationAttempt = async (
+ input: { actorId: string; authId: string },
+ outcome: "failed" | "unreconciled" | "withdrawn",
+ ) => {
+ const written = await database
+ .update(pendingAuthorizations)
+ .set({
+ completedAt: sql`date_trunc('milliseconds', now())`,
+ outcome,
+ })
+ .where(
+ and(
+ eq(pendingAuthorizations.actorId, input.actorId),
+ eq(pendingAuthorizations.authId, input.authId),
+ isNull(pendingAuthorizations.completedAt),
+ ),
+ )
+ .returning({ actorId: pendingAuthorizations.actorId });
+ return written.length > 0;
+ };
+
/**
* The token one call goes out with, and whose it is — decided from `access.credential`, so that
* this function and the audit row cannot disagree about whose account a call ran in.
@@ -432,7 +506,7 @@ export function createStoreContext(options: PluginStoreOptions) {
*/
if (!access.toolkit) {
throw new PluginInvariantError(
- `${row.id} resolves to a brokered credential with no Composio app in its url.`,
+ `${row.id} resolves to a brokered credential with no ${BROKERED_PROFILE[brokerProvider].vendor} app in its url.`,
);
}
@@ -520,6 +594,54 @@ export function createStoreContext(options: PluginStoreOptions) {
`${row.title} changed how it is shared while this call was being checked, so it was not called. Ask again.`,
null,
);
+ /*
+ * A ROW IS ONLY REQUIRED WHERE THIS DEPLOYMENT IS THE ONE HOLDING THE GRANT.
+ *
+ * CRITERION. Whether a brokered call has to find a `brokered_connections` row first is the
+ * broker's DECLARED {@link ConnectedAppBroker.grantIsLocal} — never a `provider === "arcade"`
+ * comparison, never a test of the row's provenance column, never a look at its url. The
+ * question is "where does the grant live", the field answers exactly that question, and the
+ * vendor's name is not an answer to it.
+ *
+ * REASON. The gate just below and its `none` exemption above once keyed on DIFFERENT things,
+ * and the exemption failed OPEN: a duplicate row recording `NO_AUTH` at a key app's url
+ * skipped the per-person gate entirely, and the deployment's own key ran a call for somebody
+ * who had connected nothing. A vendor's name compared inside a check whose vocabulary is
+ * about permission is that same shape — two answers to one question with nothing forcing them
+ * to agree, and a third broker would mean finding every such comparison by hand. A declared
+ * property is checkable against every adapter, reads the same as the `listNeedsCredential`
+ * beside it, and a third provider answers it by existing or fails to type-check.
+ *
+ * WHY THE ROW IS NOT REQUIRED WHERE THE GRANT IS THE VENDOR'S. Such a broker checks per
+ * action, on every call, at the vendor. A local row demanding to exist first would be a
+ * second, weaker copy of that check — and it is the copy that forces a Connect button, which
+ * forces an arbitrary first consent before anybody knows which action they want. Here the
+ * call proceeds and the vendor's own typed refusal is the authoritative answer.
+ *
+ * ONLY A PRECONDITION IS REMOVED; THE IDENTITY IS UNCHANGED. The id returned is
+ * `resolved.ref.vendorUserId` — the very value the branch below returns out of the row —
+ * derived by {@link accountRefFor} from the session's actor and the app's own mode, Personal
+ * or Shared, exactly as before. The model cannot name an identity, nothing about whose
+ * account a call runs in changes here, and the Shared case still goes out as the deployment.
+ *
+ * AND THE PROPERTY IS ONLY EVER READ ABOUT THAT BROKER'S OWN ROWS, which is not a vendor-name
+ * comparison sneaking back in — it is the question of WHOSE declaration this is.
+ *
+ * CRITERION. A brokered row is governed by the broker it was minted at. Where that is not the
+ * configured one, no declaration of the configured broker's applies to it, and the local gate
+ * stands — which refuses, because a row of the other broker's can have no connection row here.
+ *
+ * REASON. `access.transport` is the row's provenance (see `accessFor`) and `broker` is whatever
+ * this deployment is configured with, and the two are not always the same vendor: a deployment
+ * that has switched broker still holds the other's rows. Read off the active broker alone, the
+ * waiver crossed over to them — on an Arcade deployment, a left-over Composio app was called
+ * with no connection row of any kind, exempted by a per-action check at a vendor that has never
+ * heard of it, on a deployment that cannot connect, check or disconnect it at all. The
+ * `grantIsLocal` argument is about where THIS broker's grants live; it says nothing about
+ * somebody else's.
+ */
+ if (broker && !broker.grantIsLocal && access.transport === brokerProvider)
+ return { vendorUserId: resolved.ref.vendorUserId };
const [connected] = await database
.select({ vendorUserId: brokeredConnections.vendorUserId })
.from(brokeredConnections)
@@ -1402,7 +1524,7 @@ export function createStoreContext(options: PluginStoreOptions) {
if (!existing) return;
if (accessFor(existing, null).credential !== "brokered") return;
throw new CustomServerRefusedError(
- `${serverId} is a Composio app this deployment has enabled, and people may have connected their accounts to it. Remove the app first — which ends those accounts at Composio — and the name is then free.`,
+ `${serverId} is ${brokerProvider === "arcade" ? "an" : "a"} ${BROKERED_PROFILE[brokerProvider].vendor} app this deployment has enabled, and people may have connected their accounts to it. Remove the app first — which ends those accounts at ${BROKERED_PROFILE[brokerProvider].vendor} — and the name is then free.`,
);
}
@@ -1439,7 +1561,10 @@ export function createStoreContext(options: PluginStoreOptions) {
* with it, so the audience gate decides on and keys its approval by the row that answers rather
* than the one that was dialled. Null for every row that is not a brokered app.
*/
- const toolkit = row.provenance === "composio" ? toolkitOf(row.url) : null;
+ const toolkit =
+ row.provenance === brokerProvider
+ ? toolkitOfFor(brokerProvider, row.url)
+ : null;
const app = toolkit ? await brokeredAppRow(toolkit) : null;
const access = accessFor(
app
@@ -1504,7 +1629,7 @@ export function createStoreContext(options: PluginStoreOptions) {
async function brokeredAppRow(
toolkit: string,
): Promise {
- const url = `composio://${toolkit}`;
+ const url = `${BROKERED_PROFILE[brokerProvider].scheme}${toolkit}`;
return (await brokeredAppRowsAt([url])).get(url) ?? null;
}
@@ -1572,6 +1697,276 @@ export function createStoreContext(options: PluginStoreOptions) {
return schemeKind(await brokeredAppScheme(toolkit));
}
+ /**
+ * THE ONE PLACE THAT ASKS THE BROKER FOR AN AUTH PROVIDER, and so the only place that knows how
+ * many it could not get. {@link connectionSiblings} says what the answers imply; this is the
+ * question asked of the rows this deployment holds, with its one extra fact: HOW MANY APPS ON
+ * THE ASKING IDENTITY WENT UNANSWERED.
+ *
+ * THE COUNT EXISTS BECAUSE THE RULE IS ASYMMETRIC ON PURPOSE. An app the broker cannot answer for
+ * is left out rather than guessed at — naming an app that will not be touched trains people to
+ * dismiss the warning that matters (D26). But under-naming is not free: somebody presses
+ * Disconnect and loses an app nobody warned them about, and a deployment whose resolver fails
+ * systematically would do it silently and for ever. So the number is surfaced, an integer and
+ * never names, for whoever reads the trail.
+ *
+ * Counted over the OTHER apps on the asking identity: the asking app's own row is not a sibling
+ * candidate. If the asking app itself is unanswered, nothing is resolvable and every other app on
+ * the identity is unanswered with it.
+ */
+ async function resolveSiblings(
+ input: {
+ toolkit: string;
+ account: AccountRef;
+ },
+ /**
+ * ONE CALL'S MEMO OF `authProviderOf`, PASSED IN BY A CALLER THAT RESOLVES OVER MANY ACCOUNTS.
+ *
+ * Resolving an auth provider is a live vendor call with no cache of its own on Arcade
+ * (`arcade-adapter`'s `authProviderOf` lists the toolkit's tools every time); on Composio it is
+ * the toolkit name and costs nothing. A caller that walks every PERSON who connected an app —
+ * {@link removeServer} — would otherwise ask the vendor for the same handful of toolkits once
+ * per person and again once per app that person holds, which is unbounded in the number of
+ * people and times out partway through a removal that has already revoked at the vendor.
+ *
+ * A toolkit's auth provider cannot change inside one removal, so answering the second ask from
+ * the first is semantically identical — the same argument the identity pre-filter above makes
+ * for its `where`. The memo holds the PROMISE, so two resolutions of one toolkit share one
+ * in-flight call rather than racing into two.
+ *
+ * ITS LIFETIME IS ONE CALL AND IT IS THEN DISCARDED. Omitted, every resolution is asked for
+ * fresh, which is what a single-account caller wants; nothing here is a process-lifetime cache,
+ * which would be a different design with an invalidation question this one does not have.
+ */
+ memo?: Map>,
+ ): Promise<{ siblings: string[]; unanswered: number }> {
+ if (!broker) return { siblings: [], unanswered: 0 };
+ const vendor = broker;
+ const authProviderOf = (toolkit: string): Promise => {
+ if (!memo) return vendor.authProviderOf(toolkit);
+ const asked = memo.get(toolkit);
+ if (asked) return asked;
+ const asking = vendor.authProviderOf(toolkit);
+ memo.set(toolkit, asking);
+ return asking;
+ };
+ const authProvider = await authProviderOf(input.toolkit);
+ const held = await database
+ .select({
+ app: brokeredConnections.app,
+ vendorUserId: brokeredConnections.vendorUserId,
+ })
+ .from(brokeredConnections)
+ .where(
+ and(
+ eq(brokeredConnections.provider, brokerProvider),
+ eq(brokeredConnections.vendorUserId, input.account.vendorUserId),
+ ),
+ );
+ if (!authProvider) {
+ const others = new Set(
+ held.map((row) => row.app).filter((app) => app !== input.toolkit),
+ );
+ return { siblings: [], unanswered: others.size };
+ }
+ const footprints: ConnectionFootprint[] = [];
+ const unanswered = new Set();
+ for (const row of held) {
+ const rowProvider = await authProviderOf(row.app);
+ if (rowProvider) footprints.push({ ...row, authProvider: rowProvider });
+ else if (row.app !== input.toolkit) unanswered.add(row.app);
+ }
+ return {
+ siblings: connectionSiblings(
+ {
+ app: input.toolkit,
+ vendorUserId: input.account.vendorUserId,
+ authProvider,
+ },
+ footprints,
+ ),
+ unanswered: unanswered.size,
+ };
+ }
+
+ /**
+ * WHETHER A CONSENT REVOKE AT THE VENDOR REACHES THIS APP AT ALL — the one spelling of rule 1,
+ * which every judgement below about a KEY app asks rather than comparing a kind itself.
+ *
+ * A key is a credential of this deployment's own; no withdrawal at the vendor ends it. So a key
+ * app is never an app a revoke ENDED (its row stands, and no trail row names an ender for it),
+ * and never one whose trail row may say a vendor was ASKED to withdraw it. `!== "key"` keeps the
+ * reading every caller already made: an app whose scheme cannot be read, or that has no account,
+ * is reported as reached, which is the claim that cannot be too weak (see
+ * {@link disconnectBrokered}'s roster).
+ */
+ async function consentRevokeReaches(app: string): Promise {
+ return (await brokeredAppKind(app)) !== "key";
+ }
+
+ /**
+ * WHAT THE TRAIL SAYS WAS ASKED OF THE VENDOR ABOUT THE APP AN ACT WAS ABOUT — the one spelling
+ * of `vendorRevocationRequested` that is computed rather than constant.
+ *
+ * `answered` is the caller's own reading of what the vendor said: {@link disconnectBrokered}'s
+ * `ended`, {@link removeServer}'s per-account answer, {@link retireConnectionsFor}'s own answer
+ * or-ed with "a sibling's ask ended it". For a KEY app that reading is not the claim — whatever
+ * the vendor answered, it did not withdraw a key it never held — so the field is `false`. Three
+ * writers decided that three ways (round 2, A5c and L5-a): only the first said `false`.
+ *
+ * IN TWO STEPS, READ THEN ANSWER: the app's kind is read when this is called and the claim is
+ * made when the vendor's answer is handed to what it returns. Every writer reads the kind BEFORE
+ * its revoke goes out — so a failed read leaves the act before anything was asked of the vendor —
+ * and that order is kept rather than moved behind the revoke for the helper's convenience.
+ *
+ * Answers the payload fragment rather than the boolean so that no writer computes the field
+ * itself: a writer spreads this, and the guard in `sole-definition.test.ts` holds that to one
+ * home.
+ */
+ async function vendorRevocationClaim(
+ app: string,
+ ): Promise<(answered: boolean) => { vendorRevocationRequested: boolean }> {
+ const reaches = await consentRevokeReaches(app);
+ return (answered) => ({ vendorRevocationRequested: reaches && answered });
+ }
+
+ /**
+ * WHICH OF A REVOKE'S SIBLINGS THAT REVOKE ENDED — rules 1 and 2 of {@link clearEndedSiblings},
+ * as a JUDGEMENT with no write attached, so that a caller who needs the answer without the delete
+ * asks the same question rather than walking the set itself.
+ *
+ * {@link clearEndedSiblings} deletes and files what this answers (and adds rule 3, which is about
+ * a row and so belongs to the delete). {@link retireConnectionsFor} needs only the answer: it has
+ * already revoked every app the person holds and deletes them as its own acts, and what it needs
+ * from the siblings is which of its rows to annotate with `endedWith`. It used to walk the set
+ * itself with rule 2 and without rule 1, and so filed a KEY app as ended by an OAuth sibling's
+ * revoke (round 2, A5b) — trail-only, which is why no audit of the rows caught it.
+ *
+ * Deduplicated, the asking app excluded, in {@link resolveSiblings}' order.
+ */
+ async function siblingsEndedBy(input: {
+ siblings: readonly string[];
+ /** The app whose revoke this is. Never in the answer — it has its own row. */
+ endedWith: string;
+ /** What that revoke ANSWERED. Rule 2. */
+ revoked: boolean;
+ }): Promise {
+ if (!input.revoked) return [];
+ const ended: string[] = [];
+ for (const app of new Set(input.siblings)) {
+ if (app === input.endedWith) continue;
+ if (!(await consentRevokeReaches(app))) continue;
+ ended.push(app);
+ }
+ return ended;
+ }
+
+ /**
+ * THE LOCAL ROWS ONE REVOKE'S CASCADE ENDS, AND THE THREE RULES THAT DECIDE WHICH — in ONE place,
+ * because they were in two and the two disagreed about all three.
+ *
+ * WHAT A CASCADE IS. {@link resolveSiblings} answers which other apps share the connection the
+ * caller's single revoke withdrew; where a connection is keyed `(identity, auth provider)` —
+ * Arcade — that one ask ended them too, so their rows here would otherwise go on claiming
+ * accounts the vendor no longer holds. This is the write that ends them, and it is the ONLY
+ * thing in this file that deletes a row for an app NOBODY ASKED ABOUT. That is why the rules are
+ * a function rather than a convention: a row deleted here is a grant somebody still holds at the
+ * vendor and can no longer reach from this deployment, and the cost of getting it wrong is not
+ * symmetric with the cost of leaving a row standing.
+ *
+ * RULE 1 — AN APP THIS DEPLOYMENT HOLDS A KEY FOR IS NOT WHAT THAT REVOKE ENDED, so its row
+ * stands and the trail says nothing about it. The withdrawal ended an OAuth connection; a key is
+ * a credential of this deployment's own and no consent revoke at the vendor reaches it. Deleting
+ * it destroys a credential nothing withdrew and leaves a LIVE account with no local row to reach
+ * it; recording it claims a vendor ended something it never held — the reading
+ * {@link disconnectBrokered} writes `vendorRevocationRequested: false` for, there on an act that
+ * really was about that app. The scheme is read off `mcp_servers`, which this never deletes from.
+ *
+ * RULE 2 — ONLY FROM A REVOKE THAT ANSWERED TRUE (`revoked`). `false` means nothing was found to
+ * withdraw, or the auth provider could not be resolved, and the vendor was not shown to have
+ * ended ANYTHING; a sibling's row is then left standing rather than claimed gone on the strength
+ * of an answer nobody got. It is a parameter and not a caller's discipline because a caller's
+ * discipline is exactly what one of the two callers did not keep.
+ *
+ * RULE 3 — ONLY A ROW THAT WAS ACTUALLY HERE GETS A TRAIL ROW (`if (!deleted) continue`). These
+ * rows exist to explain why an app's connection VANISHED from this deployment's records, and an
+ * app that had no row here had nothing to explain. A sibling resolved on this identity but held
+ * by somebody else — {@link resolveSiblings} reads the identity, {@link accountRow} the holder —
+ * is exactly that case, and it used to get a row saying it had been disconnected.
+ *
+ * THE ONLY SPELLING OF THESE RULES. Three acts cascade a revoke onto its siblings —
+ * {@link disconnectBrokered}, {@link clearWithdrawnAccount} and {@link removeServer} — and all
+ * three call this. {@link removeServer}'s loop was the oldest spelling and kept only 2 and 3 for
+ * itself, deleting a KEY sibling's row on a removal; routing it here closed that (round 2, A5a).
+ * A fourth cascade belongs here too, not beside it. Rules 1 and 2 themselves are
+ * {@link siblingsEndedBy}'s, which this asks and which {@link retireConnectionsFor} asks for the
+ * one thing it needs without the delete — the `endedWith` it files — so the judgement about which
+ * siblings a revoke ended has one home whether or not a row is deleted on it (round 2, K5).
+ *
+ * `vendorRevocationRequested` IS TRUE ON EVERY ROW THIS WRITES AND IS NOT A GUESS: rule 2 says a
+ * revoke answered true, and that ask ended the siblings with the app it named. `false` means "no
+ * vendor was asked", which is the one thing none of these rows may say. `endedWith` names the app
+ * that asked — the only thing that tells a reader why a connection they never touched is gone —
+ * and `unansweredSiblings` qualifies that claim, beside it and nowhere else.
+ *
+ * IT ASKS NOTHING OF ANY VENDOR. The caller revoked, before it called this; see each caller.
+ *
+ * Answers the apps it actually cleared, which is what {@link clearWithdrawnAccount} returns and
+ * what a caller needs in order to say what went.
+ */
+ async function clearEndedSiblings(input: {
+ /** {@link resolveSiblings}' answer for `endedWith`. Deduplicated here; order is its own. */
+ siblings: readonly string[];
+ account: AccountRef;
+ /** The app whose revoke this cascade belongs to. Never cleared here — it has its own row. */
+ endedWith: string;
+ /** What that revoke ANSWERED. Rule 2. */
+ revoked: boolean;
+ /** {@link resolveSiblings}' count, an integer and never names. */
+ unanswered: number;
+ by: string;
+ /**
+ * The trail's one word for why, from the closed set the writers of this event use. Wider than
+ * any one caller's own `reason`, so {@link removeServer} passes `mcp_server_removed` through
+ * the same parameter the other two pass theirs.
+ */
+ reason: BrokeredDisconnectReason;
+ }): Promise {
+ // Rules 1 and 2, and the dedupe, are {@link siblingsEndedBy}'s; rule 3 is the delete's.
+ const ended = await siblingsEndedBy(input);
+ const cleared: string[] = [];
+ for (const app of ended) {
+ // `returning` because whether a row was here is the whole of rule 3, and a delete that
+ // answered nothing would leave the two cases indistinguishable.
+ const [deleted] = await database
+ .delete(brokeredConnections)
+ .where(accountRow(app, input.account))
+ .returning({ app: brokeredConnections.app });
+ if (!deleted) continue;
+ cleared.push(app);
+ await recordAuditEvent(auditStore, {
+ eventType: "mcp.account_disconnected",
+ targetType: "mcp_server",
+ targetId: app,
+ payload: {
+ actor: input.by,
+ server: app,
+ // Whose account this was, which is not always who ended it; named as the deployment
+ // where there is no person behind it.
+ owner:
+ input.account.holder === "person"
+ ? input.account.userId
+ : "deployment",
+ reason: input.reason,
+ vendorRevocationRequested: true,
+ unansweredSiblings: input.unanswered,
+ endedWith: input.endedWith,
+ },
+ });
+ }
+ return cleared;
+ }
+
return {
options,
database,
@@ -1609,6 +2004,13 @@ export function createStoreContext(options: PluginStoreOptions) {
brokeredAppRowsAt,
brokeredAppScheme,
brokeredAppKind,
+ brokerProvider,
+ closeAuthorizationAttempt,
+ resolveSiblings,
+ consentRevokeReaches,
+ vendorRevocationClaim,
+ siblingsEndedBy,
+ clearEndedSiblings,
};
}
diff --git a/server/src/plugins/plugin-store/model.ts b/server/src/plugins/plugin-store/model.ts
index 69ef78b10..4702cfe8d 100644
--- a/server/src/plugins/plugin-store/model.ts
+++ b/server/src/plugins/plugin-store/model.ts
@@ -1,3 +1,4 @@
+import { and, eq, sql } from "drizzle-orm";
import type { ApprovalGate } from "../../approvals/types";
import type { AuditStore } from "../../audit";
import type { ActionPolicy } from "../../computer/policy";
@@ -7,8 +8,14 @@ import type {
} from "../../credentials";
import type { Database } from "../../db/client";
import { isQueryFailure, withoutStatement } from "../../db/query-failure";
+import { pendingAuthorizations } from "../../db/schema";
import type { CheckPrivateShare } from "../../proactive/private-share";
-import { type ServerAccess, ServerUnresolvableError } from "../access";
+import {
+ BROKERED_PROFILE,
+ type BrokerProvider,
+ type ServerAccess,
+ ServerUnresolvableError,
+} from "../access";
import type {
BrokerConnection,
ComposioBroker,
@@ -607,6 +614,7 @@ export function storableTools(serverId: string, listed: ListedTool[]) {
{
serverId: string;
name: string;
+ vendorName: string;
description: string;
inputSchema: Record;
effect: "read" | "write" | null;
@@ -617,10 +625,35 @@ export function storableTools(serverId: string, listed: ListedTool[]) {
for (const tool of listed) {
const name = tool.name.replaceAll(NUL, "");
+ /*
+ * THE GUARD THAT MAKES THE OFFERED NAME OFFERABLE, placed at the one seam every transport's
+ * listing passes through on its way into the database.
+ *
+ * A model tool name may only match `^[a-zA-Z0-9_-]+$`, and `name` is spliced into one by
+ * {@link toolNameFor}. Nothing used to check: Arcade's dotted `qualified_name` was recorded
+ * whole and the refusal arrived much later and somewhere else entirely — as a 400 on the model
+ * request naming `tools[47]`, with no mention of the app, the action or the refresh that
+ * recorded it. A person reading that has nothing to act on.
+ *
+ * SO IT REFUSES HERE, WHERE THE NAME HAS A PROVENANCE. `refreshTools` runs this before its
+ * transaction and maps a throw to "nothing was refreshed and the actions already recorded are
+ * kept", so an app that starts publishing an unofferable name keeps working with what it had
+ * and says what is wrong on its own page.
+ */
+ if (!OFFERABLE_TOOL_NAME.test(name)) {
+ throw new PluginInvariantError(
+ `${serverId} published an action named "${name}", which cannot be offered to a model: a tool name may only use letters, digits, underscores and hyphens. Nothing was refreshed and the actions already recorded for this app are kept.`,
+ );
+ }
if (byName.has(name)) continue;
byName.set(name, {
serverId,
name,
+ /*
+ * The vendor's spelling where the transport drew a distinction, and the same string where it
+ * did not — which is the whole of what absence means here. See {@link ListedTool.vendorName}.
+ */
+ vendorName: (tool.vendorName ?? name).replaceAll(NUL, ""),
/*
* Defaulted where the COLUMN has a default, because that is what the previous mapping leaned
* on: it passed these two straight through, so a transport handing back undefined got the
@@ -715,6 +748,16 @@ export const INVALID_CLIENT = "invalid_client";
*/
export type Transaction = Parameters[0]>[0];
+/**
+ * THE ONLY SHAPE A MODEL TOOL NAME MAY TAKE, as the model API enforces it: `^[a-zA-Z0-9_-]+$`.
+ *
+ * Declared here beside {@link toolNameFor}, which is the function that has to satisfy it, and
+ * applied in {@link storableTools}, which is the one door every transport's listing goes through.
+ * The prefix and separator this module splices in are themselves made of these characters, so a
+ * `name` that passes is an offered name that passes.
+ */
+export const OFFERABLE_TOOL_NAME = /^[a-zA-Z0-9_-]+$/;
+
/**
* A tool name the model can actually call.
*
@@ -1046,6 +1089,23 @@ export const CLIENT_REREGISTRATION_BACKOFF_MS = 5 * 60_000;
export const IDENTITY_ACTION =
/(_GET_ME|_PROFILE|_CURRENT_USER|_USER_INFO|_WHOAMI)$/;
+/**
+ * Every broker's id namespace, reserved together. See {@link BROKERED_PROFILE} in `./access` for
+ * what a profile holds and why it lives there; this is only the list this file reserves off it.
+ */
+export const BROKERED_ID_PREFIXES = Object.values(BROKERED_PROFILE);
+
+/**
+ * Every broker's screen name, reserved together, for the reason {@link BROKERED_ID_PREFIXES} is.
+ *
+ * THE KEYS, NOT A SECOND LITERAL LIST. `/admin/plugins/composio` is the app directory's own page and
+ * `/admin/plugins/arcade` is the Arcade one, and a static route is matched ahead of
+ * `/admin/plugins/$key` on both — so a server sitting at either id would be listed and then open
+ * somebody else's page instead of its own. Spelled as its own list, this reservation would be the
+ * one somebody adds a broker to {@link BROKERED_PROFILE} without adding to.
+ */
+export const BROKERED_SCREEN_NAMES = Object.entries(BROKERED_PROFILE);
+
/** What a vendor's token endpoint gave back for a refresh token. */
export type AccessToken = {
accessToken: string;
@@ -1128,6 +1188,34 @@ export type PluginStoreOptions = {
* above — invent a broker to get a store.
*/
broker?: ComposioBroker;
+ /**
+ * Which broker this deployment runs, which is the slice of `brokered_connections` this store
+ * reads and the word its connection writer stamps on every row it mints.
+ *
+ * THE ONE PLACE THE VENDOR IS NAMED. Every read of `brokered_connections` below and the insert in
+ * {@link createPluginStore}'s `recordBrokeredConnection` used to spell `composio` for themselves —
+ * eleven comparisons that were each correct while there was one broker and each silently wrong on
+ * the day there were two. They now all read this. A deployment running Arcade whose store was
+ * still filtering on `composio` would not fail: it would read an empty slice, write rows nobody
+ * reads back, and leave the "the other provider holds rows" refusal permanently unfired while the
+ * offboarding sweep swept the wrong table slice. That is the failure this field exists to make
+ * impossible to reach by accident.
+ *
+ * REQUIRED, UNLIKE `broker` ABOVE, BECAUSE FORGETTING IT HAS NO SYMPTOM. An absent `broker` says
+ * one true thing — this deployment has not configured one — and every path that needs it raises.
+ * An absent provider would have had to be guessed, and the only guess available was `composio`:
+ * an Arcade deployment built by forgetting this field would then read and write the COMPOSIO slice
+ * of `brokered_connections`, hand its own rows back through a composio filter, and APPEAR TO WORK.
+ * Spec D3's "the other provider holds rows" refusal would never fire, because nothing would ever
+ * be filed under the other name, and the offboarding sweep would sweep a slice that is not this
+ * deployment's. Nothing goes red. There is no error to raise later, so the compiler asks now.
+ *
+ * SO THERE IS NO DEFAULT ANYWHERE — not here, not in {@link createPluginStore}, not in a test
+ * helper. A function that cannot reach the provider takes it as a parameter; a second answer to
+ * "which provider" is a second thing that can be wrong while agreeing with nothing. `activeProvider`
+ * in `./broker` decides it from configuration at the one deployment boundary that knows.
+ */
+ brokerProvider: BrokerProvider;
/** Where the vendor sends people back; needed to (re)register a dynamic client. */
redirectUri?: string;
/**
@@ -1137,6 +1225,157 @@ export type PluginStoreOptions = {
deploymentId?: string;
};
+/** One in-flight Arcade authorization attempt, as the table stores it. */
+export type PendingAuthorization = typeof pendingAuthorizations.$inferSelect;
+
+/**
+ * WHY A BROKERED ACCOUNT'S ROW WENT, as the trail spells it, and a closed set rather than free
+ * text for the reason {@link PluginStore.disconnectBrokered} gives: a reader asking the trail
+ * WHICH of these happened can be answered only if it is the same word every time.
+ *
+ * ONE SET ACROSS EVERY WRITER OF `mcp.account_disconnected`, so that `clearEndedSiblings` — which
+ * writes for all of them — takes the word rather than re-deciding it. Each writer still narrows
+ * this to the words IT may use; nothing takes the whole union as its own input.
+ */
+export type BrokeredDisconnectReason =
+ /** The person disconnected their own account. */
+ | "self"
+ /** The person was removed from the People screen; {@link PluginStore.retireConnectionsFor}. */
+ | "person_removed"
+ /** An administrator disconnected the deployment's account for a Shared app. */
+ | "admin"
+ /** The app was switched between Personal and Shared, retiring the old mode's accounts. */
+ | "mode_switched"
+ /** A consent's context moved on and the verifier withdrew the retired identity's app. */
+ | "context_changed"
+ /** The app itself was removed; {@link PluginStore.removeServer}. */
+ | "mcp_server_removed";
+
+/** One app's connection, as far as sibling-finding needs to know it. */
+export type ConnectionFootprint = {
+ /** The toolkit, spelled as the vendor spells it. Compared exactly, never case-folded. */
+ app: string;
+ /** The identity the connection is held under at the vendor. */
+ vendorUserId: string;
+ /**
+ * The vendor's own AUTH PROVIDER the connection is held under (`google`), not the broker's name.
+ *
+ * NAMED `authProvider` AND NEVER `providerId`, because `provider` in this file means the BROKER —
+ * `brokered_connections.provider` is read a few lines below this one — and that collision has
+ * already been paid for once in an audit payload. See {@link ConnectedAppBroker.authProviderOf},
+ * which is where this value comes from and which is likewise not the toolkit name.
+ */
+ authProvider: string;
+};
+
+/**
+ * The apps that share `of`'s connection, which are the apps sharing BOTH its vendor identity AND
+ * its AUTH PROVIDER. NEVER THE PROVIDER ALONE (spec D26).
+ *
+ * REASON. An Arcade connection is keyed `(user_id, provider_id)`, and this deployment holds MANY
+ * vendor identities: each person's own, PLUS ONE RANDOM IDENTITY PER SHARED APP, minted by
+ * `mintDeploymentVendorUserId`. So shared Gmail and shared Slack, both using Google, do NOT share
+ * a connection: their `user_id`s differ. A computation keyed on `provider_id` alone would warn
+ * about apps that WILL NOT BE TOUCHED, which trains people to dismiss the warning that matters.
+ * Silently ending two connections when somebody asked to end one is the failure to avoid.
+ *
+ * The auth provider is compared case-insensitively, as the adapter's `connectionsFor` does; the app
+ * name is not, because Arcade toolkit case is never normalised. The answer never contains `of`
+ * itself, and is sorted so two callers see the same list. This function ACTS ON NOTHING.
+ */
+export function connectionSiblings(
+ of: ConnectionFootprint,
+ among: readonly ConnectionFootprint[],
+): string[] {
+ const provider = of.authProvider.toLowerCase();
+ const found = new Set();
+ for (const other of among) {
+ if (other.app === of.app) continue;
+ if (other.vendorUserId !== of.vendorUserId) continue;
+ if (other.authProvider.toLowerCase() !== provider) continue;
+ found.add(other.app);
+ }
+ return [...found].sort();
+}
+
+/** Raw `execute` rows are snake_case and carry timestamps as whatever the driver decoded. */
+export function pendingAuthorizationFromRow(
+ row: Record,
+): PendingAuthorization {
+ const when = (v: unknown) =>
+ v === null || v === undefined ? null : new Date(v as string | Date);
+ return {
+ actorId: row.actor_id as string,
+ serverId: row.server_id as string,
+ action: row.action as string,
+ holder: row.holder as PendingAuthorization["holder"],
+ userId: (row.user_id as string | null) ?? null,
+ vendorUserId: row.vendor_user_id as string,
+ authId: (row.auth_id as string | null) ?? null,
+ createdAt: when(row.created_at) as Date,
+ completedAt: when(row.completed_at),
+ outcome: (row.outcome as PendingAuthorization["outcome"]) ?? null,
+ };
+}
+
+/**
+ * The instant a caller names an authorization attempt by, as a `Date`, or a refusal naming the
+ * field.
+ *
+ * THE DECLARED TYPE IS `Date` AND THE VALUE IS NOT ALWAYS ONE. `PendingAuthorization.createdAt` is
+ * typed `Date` and is one while it stays in this process, but an attempt that has been through
+ * JSON — serialised into a job, a cache or a cross-process hop and parsed back — carries an ISO
+ * STRING under a `Date` annotation. Handed to drizzle, that string reaches
+ * `PgTimestamp.mapToDriverValue`, which calls `.toISOString()` on it and raises
+ * `TypeError: value.toISOString is not a function` from inside the driver: a failure that names
+ * neither the field nor the caller. An ISO string names the same instant perfectly well, so it is
+ * accepted; anything that names NO instant is refused HERE, where the message can say what was
+ * wrong, rather than running as a query that silently matches nothing.
+ */
+function attemptInstant(createdAt: Date | string): Date {
+ const instant =
+ createdAt instanceof Date
+ ? createdAt
+ : typeof createdAt === "string"
+ ? new Date(createdAt)
+ : new Date(Number.NaN);
+ if (Number.isNaN(instant.getTime())) {
+ throw new Error(
+ `An authorization attempt's created_at must be a Date or an ISO timestamp, not ${JSON.stringify(createdAt) ?? String(createdAt)}.`,
+ );
+ }
+ return instant;
+}
+
+/**
+ * The condition that names ONE actor's attempt by the `created_at` a caller holds.
+ *
+ * MATCHED AT MILLISECOND PRECISION ON BOTH SIDES, BECAUSE THE TWO SIDES DO NOT CARRY THE SAME ONE.
+ * `created_at` is a `timestamptz`, which PostgreSQL keeps to the MICROSECOND, and the `Date` the
+ * driver hands back carries MILLISECONDS. A row stored at `…13.407577` is read back as
+ * `…13.407Z`, and an exact `=` against that `Date` matches NOTHING — so the mint writes no
+ * `auth_id`, and per {@link pendingAuthorizations}, "an attempt holding null can never complete":
+ * a silently dead attempt that waits out its fifteen minutes.
+ *
+ * {@link createPluginStore}'s `claimAuthorizationAttempt` writes `date_trunc('milliseconds', now())`
+ * for exactly this reason, but THAT ONLY HOLDS FOR THE ONE WRITER THAT REMEMBERS. The column's
+ * default is `now()` at full precision, which is what drizzle's inferred insert type invites any
+ * other writer — a fixture, a backfill, a future route — to take. Truncating the COLUMN in the
+ * comparison holds whatever wrote the row, which is why the invariant lives here rather than in
+ * the schema: it needs no migration and no writer's cooperation.
+ *
+ * TRUNCATING CAN NEVER MATCH A DIFFERENT ROW: `actor_id` is the table's PRIMARY KEY, so there is at
+ * most one attempt per actor and `created_at` is the staleness guard on that one row, never a
+ * selector among several. It still refuses a value naming an OLDER attempt, which is what stops a
+ * late discard from deleting the replacement that took the key.
+ */
+export function attemptNamed(actorId: string, createdAt: Date | string) {
+ return and(
+ eq(pendingAuthorizations.actorId, actorId),
+ sql`date_trunc('milliseconds', ${pendingAuthorizations.createdAt}) = ${attemptInstant(createdAt).toISOString()}::timestamptz`,
+ );
+}
+
/**
* The literal recorded on the row, which is the scheme a later call must keep using.
*
diff --git a/server/src/plugins/plugin-store/servers.ts b/server/src/plugins/plugin-store/servers.ts
index 661b1d04d..7bc883405 100644
--- a/server/src/plugins/plugin-store/servers.ts
+++ b/server/src/plugins/plugin-store/servers.ts
@@ -13,7 +13,7 @@ import {
mcpTools,
pluginGrants,
} from "../../db/schema";
-import { accessFor } from "../access";
+import { BROKERED_PROFILE, brokeredProvenance, toolkitOfFor } from "../access";
import {
type BrokerConnection,
BrokerUnconfiguredError,
@@ -28,12 +28,17 @@ import {
serverCredentialKind,
tokenHeaderFor,
} from "../catalogue";
-import { toolkitOf } from "../composio";
import type { ListedTool } from "../mcp";
-import type { AccountMode, AccountRef } from "../shared-accounts";
+import {
+ type AccountMode,
+ type AccountRef,
+ asAccountRef,
+} from "../shared-accounts";
import { transportFor } from "../transport";
import type { StoreContext } from "./context";
import {
+ BROKERED_ID_PREFIXES,
+ BROKERED_SCREEN_NAMES,
CatalogueEntryUnknownError,
CustomServerRefusedError,
DEPLOYMENT_ACTOR,
@@ -64,8 +69,24 @@ export function serverMethods(context: StoreContext) {
brokeredAppRow,
brokeredAppRowsAt,
brokeredAppScheme,
+ brokerProvider,
+ resolveSiblings,
+ vendorRevocationClaim,
+ clearEndedSiblings,
} = context;
return {
+ /**
+ * The one provider this store's rows belong to, readable by the layer above it.
+ *
+ * EXPOSED SO THE ROUTES DO NOT DECIDE IT A SECOND TIME. `routes.ts` used to take the name off
+ * the broker it was handed and write `?? "composio"` when there was none — which is a second
+ * answer to the question {@link PluginStoreOptions.brokerProvider} argues must have exactly one.
+ * The two came apart precisely where it hurt: a deployment with both keys set and
+ * `OPENBOT_BROKER_PROVIDER` unset is wired with no broker at all, so the routes read every
+ * `arcade://` row of this store with Composio's parser. Read from here, the parser the routes
+ * use and the filter this store queries with cannot disagree.
+ */
+ brokerProvider,
accountRefFor,
/**
* Add a server from the catalogue.
@@ -227,23 +248,32 @@ export function serverMethods(context: StoreContext) {
/*
* Nor may it take the name of a screen. `/admin/plugins/composio` is a static route — the app
- * directory's own page — and a static route is matched ahead of `/admin/plugins/$key`, so a
- * server sitting at this id would be listed and then open somebody else's page instead of its
- * own. Brokered servers are `composio-` and no catalogue entry is called this, which
- * leaves a hand-typed id as the only way to reach it.
+ * directory's own page — and `/admin/plugins/arcade` is the Arcade equivalent, and a static
+ * route is matched ahead of `/admin/plugins/$key`, so a server sitting at either id would be
+ * listed and then open somebody else's page instead of its own. Brokered servers are
+ * `-` and no catalogue entry is called either of these, which leaves a
+ * hand-typed id as the only way to reach them.
+ *
+ * BOTH, ON WHICHEVER BROKER THIS DEPLOYMENT RUNS, and derived from
+ * {@link BROKERED_SCREEN_NAMES} rather than from a literal — the same argument the id-prefix
+ * loop six lines below makes for its own namespace: the screen is there to be collided with
+ * whether or not this deployment brokers through it today, and a reservation spelled twice is
+ * the one that gets added to once. Each refusal still names the id it refused.
*/
- if (input.id === "composio") {
- throw new CustomServerRefusedError(
- "composio is the name of this deployment's own Composio screen, so a server added there could never be opened. Choose another.",
- );
+ for (const [screen, { vendor }] of BROKERED_SCREEN_NAMES) {
+ if (input.id === screen) {
+ throw new CustomServerRefusedError(
+ `${screen} is the name of this deployment's own ${vendor} screen, so a server added there could never be opened. Choose another.`,
+ );
+ }
}
/*
* NOR THE NAMESPACE THE BROKERED ROWS ARE MINTED IN, which is the reservation above one step
* further out.
*
- * CRITERION. No server added by URL may take an id beginning `composio-`, whether or not a row
- * is sitting there today.
+ * CRITERION. No server added by URL may take an id beginning `composio-` or `arcade-`, whether
+ * or not a row is sitting there today, and on whichever broker this deployment runs.
*
* REASON. {@link addBrokeredApp} composes its id as `composio-` from the app an
* administrator enabled, so that space is already spoken for by a path that writes
@@ -262,10 +292,12 @@ export function serverMethods(context: StoreContext) {
* and a policy rule are written against, so a row shadowing another path's namespace inherits
* rules that were written about something else.
*/
- if (input.id.startsWith("composio-")) {
- throw new CustomServerRefusedError(
- `Names beginning composio- belong to the Composio apps this deployment enables, so ${input.id} is not a name a server added by URL can take. Choose another.`,
- );
+ for (const { idPrefix, vendor } of BROKERED_ID_PREFIXES) {
+ if (input.id.startsWith(idPrefix)) {
+ throw new CustomServerRefusedError(
+ `Names beginning ${idPrefix} belong to the ${vendor} apps this deployment enables, so ${input.id} is not a name a server added by URL can take. Choose another.`,
+ );
+ }
}
if (!/^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$/.test(input.id)) {
throw new CustomServerRefusedError(
@@ -439,10 +471,10 @@ export function serverMethods(context: StoreContext) {
*
* NO URL IS TAKEN FROM A CALLER, WHICH IS WHY THERE IS NO HOST RULE HERE. `addCustomServer`
* guards the address because the address is what an administrator typed and what a credential
- * would then be spent at; this one composes `composio://` itself, and a brokered row is
- * never dialled at a host at all — the transport reads the app off that url and asks Composio,
- * over the deployment's own key. So the only thing left to check about the url is that it says
- * what this call meant, and {@link toolkitOf} is what checks it: the slug goes in, the url comes
+ * would then be spent at; this one composes `://` itself from the store's provider, and a brokered row is
+ * never dialled at a host at all — the transport reads the app off that url and asks the active
+ * broker, over the deployment's own key. So the only thing left to check about the url is that it says
+ * what this call meant, and {@link toolkitOfFor} is what checks it: the slug goes in, the url comes
* back out through the very function `accessFor` will read it with, and a slug those two
* disagree about is refused rather than stored. A pattern written here instead would be a second
* opinion about the shape of an app name, and the reading that decides which app a call runs
@@ -481,12 +513,15 @@ export function serverMethods(context: StoreContext) {
}): Promise {
// Before anything at all. A deployment with no key has no catalogue for this app to have been
// chosen from, so there is nothing here to half-do and nothing to say but the setting.
- if (!broker) throw new BrokerUnconfiguredError();
+ if (!broker)
+ throw new BrokerUnconfiguredError(brokerProvider, "operator");
- const url = `composio://${input.slug}`;
- if (toolkitOf(url) !== input.slug) {
+ const profile = BROKERED_PROFILE[brokerProvider];
+ const url = `${profile.scheme}${input.slug}`;
+ const rowId = `${profile.idPrefix}${input.slug}`;
+ if (toolkitOfFor(brokerProvider, url) !== input.slug) {
throw new CustomServerRefusedError(
- `${input.slug} is not a name a Composio app can have. An app is named in letters, numbers, underscores and hyphens, because that name is read back out of this row's url to decide which app a call is against.`,
+ `${input.slug} is not a name ${brokerProvider === "arcade" ? "an" : "a"} ${profile.vendor} app can have. An app is named in letters, numbers, underscores and hyphens, because that name is read back out of this row's url to decide which app a call is against.`,
);
}
@@ -547,14 +582,14 @@ export function serverMethods(context: StoreContext) {
await database
.insert(mcpServers)
.values({
- id: `composio-${input.slug}`,
+ id: rowId,
title: input.title,
logo: input.logo ?? null,
// The broker, whoever publishes the app behind it. `vendor` is what the first-party rule
// is checked against, and Composio is who this deployment is actually talking to.
- vendor: "Composio",
+ vendor: profile.vendor,
url,
- provenance: "composio",
+ provenance: brokerProvider,
// Nothing for the vault to hold. A brokered call runs as the person asking, on their own
// connection at the vendor, which is a `composio_connections` row rather than a secret.
credentialId: null,
@@ -628,8 +663,8 @@ export function serverMethods(context: StoreContext) {
* host somebody typed is answering that rule about a server this deployment no longer
* dials.
*/
- provenance: "composio",
- vendor: "Composio",
+ provenance: brokerProvider,
+ vendor: profile.vendor,
addedBy: input.by,
updatedAt: new Date(),
/*
@@ -679,8 +714,7 @@ export function serverMethods(context: StoreContext) {
* insert above and this read disagree about what was stored; it is respelled rather than held
* in a binding so that nothing below can take it by mistake.
*/
- const answering =
- (await brokeredAppRow(input.slug))?.id ?? `composio-${input.slug}`;
+ const answering = (await brokeredAppRow(input.slug))?.id ?? rowId;
/**
* WHICH ROW EACH ACT OF AN ENABLE NAMES, WRITTEN DOWN BECAUSE NOTHING ELSE CAN SAY IT.
@@ -764,41 +798,83 @@ export function serverMethods(context: StoreContext) {
.from(brokeredConnections)
.where(
and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.app, input.slug),
),
)
.limit(1);
if (configured !== "standing" && connections.length === 0) {
- /*
- * MODE PICKED FOR THE PERSON ONLY WHERE THIS REWRITE IS WHAT GIVES THE APP AN ACCOUNT AT
- * ALL, AND NEVER WHERE ONE WAS ALREADY CHOSEN. A row enabled `no-auth` has `accountMode`
- * null — there was nothing to hold a mode about, see the insert above — and if this same
- * re-add is what turns it into a key or consent app, it needs the ordinary default an
- * insert would have given it, or every later read that asks "personal or shared" finds
- * null and has no answer. An administrator who already chose Shared keeps that choice.
- */
- const [current] = await database
- .select({ accountMode: mcpServers.accountMode })
- .from(mcpServers)
- .where(eq(mcpServers.id, answering))
- .limit(1);
- const bumpAccountMode =
- schemeKind(recorded) === "none" &&
- schemeKind(scheme) !== "none" &&
- (current?.accountMode ?? null) === null;
-
await database
.update(mcpServers)
- .set({
- authScheme: scheme,
- updatedAt: new Date(),
- ...(bumpAccountMode ? { accountMode: "personal" as const } : {}),
- })
+ .set({ authScheme: scheme, updatedAt: new Date() })
.where(eq(mcpServers.id, answering));
}
+ /*
+ * AN APP WITH AN ACCOUNT LEAVES THIS METHOD SAYING WHOSE, ALWAYS — AND THAT IS A SEPARATE
+ * STATEMENT FROM THE SCHEME ABOVE FOR A REASON.
+ *
+ * CRITERION. After `addBrokeredApp` returns, the row that answers for the app either records
+ * a mode or is an app with no account to hold one about ({@link schemeKind} `none`). There is
+ * no third state, and in particular no enabled key-or-consent app with a null here.
+ *
+ * REASON. The null is not a cosmetic gap: {@link accountFor} refuses a call against such a
+ * row as `ambiguous` — "does not say whether it is reached through each person's account or a
+ * shared one, so it was not called" — so an app in that state is enabled, listed, connectable
+ * and dead. THREE THINGS HAD TO BE TRUE AT ONCE FOR IT TO HAPPEN AND ALL THREE WERE. The
+ * INSERT above settles the column, but this method's second branch is the upsert's CONFLICT
+ * arm, which writes the url and the provenance — it CONVERTS a row that was not brokered
+ * into one that is — and never wrote this column at all. The guard that stood here then asked
+ * `schemeKind(recorded) === "none"`, which is the question "was this a no-auth app a moment
+ * ago"; `recorded` is {@link brokeredAppScheme}, and a row that was not brokered a moment ago
+ * answers `null`, which is `unreadable` rather than `none`. And the whole block was gated on
+ * `configured !== "standing"`, so the one obvious remedy — press Add again — reaches it only
+ * on the single press that created the config and never afterwards. An administrator
+ * converting a hand-added or restored row got an app that refused every call, and nothing
+ * they could press repaired it: the Plugins page offers "Make shared" for a null mode (it
+ * reads any non-`shared` value as Personal) and no way to say Personal at all.
+ *
+ * SO THE QUESTION ASKED HERE IS THE ONE THE REFUSAL ASKS, and nothing else: does the row
+ * answering for this app have an account to hold, and does it fail to say whose. The scheme
+ * is re-read rather than taken from `scheme` because the branch above may or may not have
+ * written it, and what matters is what now STANDS on the answering row — including the
+ * `unreadable` null a converted row carries, which {@link accountFor} treats as having an
+ * account and which is therefore exactly as dead without a mode as a recorded `OAUTH2`.
+ *
+ * `personal` IS THE FILL, which is the insert's own default and the conservative one: it
+ * reaches each asking person's own account. `shared` would hand everybody one account nobody
+ * chose to share.
+ *
+ * AND NEVER OVER A MODE THAT IS THERE. The `IS NULL` lives in the WHERE rather than in a
+ * read-then-write, so an administrator's Shared choice cannot be lost to this statement, nor
+ * to a switch racing it.
+ *
+ * ON THE ANSWERING ROW, WHICH IS WHY THE CONFLICT ARM STILL DOES NOT WRITE THE COLUMN. Every
+ * reader of a brokered app's mode — {@link brokeredAppRowsAt} and so `serverAddress`, the
+ * listing, the call gate — resolves it off the row that answers for the url, which is not
+ * always the row the upsert keyed on. A fill in the conflict arm would settle the minted row
+ * and leave the one that is actually read still null.
+ *
+ * ROWS ALREADY STUCK IN THE NULL STATE ARE NOT REACHED FROM HERE AT ALL, because an enabled
+ * app cannot be re-added without first being removed. They are repaired by
+ * `0059_settle_brokered_account_mode`, which is the same decision written once against the
+ * table; see `tests/brokered-account-mode-backfill.integration.test.ts`.
+ */
+ const [settled] = await database
+ .select({ authScheme: mcpServers.authScheme })
+ .from(mcpServers)
+ .where(eq(mcpServers.id, answering))
+ .limit(1);
+ if (schemeKind(settled?.authScheme ?? null) !== "none") {
+ await database
+ .update(mcpServers)
+ .set({ accountMode: "personal", updatedAt: new Date() })
+ .where(
+ and(eq(mcpServers.id, answering), isNull(mcpServers.accountMode)),
+ );
+ }
+
await recordAuditEvent(auditStore, {
eventType: "configuration.changed",
targetType: "mcp_server",
@@ -818,7 +894,7 @@ export function serverMethods(context: StoreContext) {
// Named for the same reason the custom path names its own: "who enabled an app whose
// actions nobody reviewed" is a question somebody will ask, and the answer should not
// require knowing how ids were spelled in a past build.
- provenance: "composio",
+ provenance: brokerProvider,
},
});
@@ -979,7 +1055,9 @@ export function serverMethods(context: StoreContext) {
* KEYED ON THE APP THE URL NAMES, exactly as `connectionTokenFor` keys the gate. `row.id` is a
* display key and nothing holds it equal to the slug in the url, so a delete by id would clear
* some other app's connections, or none, for the very row shape that gate already refuses to
- * trust. `accessFor` is asked rather than the url parsed here, so this cannot drift from it.
+ * trust. The slug is read with {@link toolkitOfFor} — the one per-provider parser `accessFor`
+ * itself reads it with — so this cannot drift from what the gate resolves; see the comment on
+ * `rowProvider` below for why the PROVIDER is read beside it rather than the slug alone.
*
* ASKED WITH NO ENTRY, deliberately, and that is not the entry-wins order being dodged. An
* entry can only ever SUPPRESS this answer — `accessFor` returns a null toolkit for every row
@@ -1017,9 +1095,52 @@ export function serverMethods(context: StoreContext) {
* deployment holds at Composio, not an account — while a live account whose config has
* already been deleted is access that nothing left here can end.
*/
- const toolkit = existing ? accessFor(existing, null).toolkit : null;
-
- if (toolkit) {
+ /*
+ * AND ALL OF IT IS ABOUT THE BROKER THE ROW WAS MINTED AT, which is not always the one this
+ * deployment is configured with.
+ *
+ * CRITERION. One removal names ONE vendor. The app slug, the connection rows it clears and
+ * the account it ends are all that row's own broker's, and a vendor is asked only where it is
+ * the one this deployment can reach.
+ *
+ * REASON. These three were read three different ways in one operation: the slug off the ROW's
+ * provenance (through `accessFor`, which parses the url with the parser belonging to whatever
+ * that column says), the connection rows off the CONFIGURED `brokerProvider`, and the
+ * revocation off the ACTIVE broker. A deployment that has switched broker — or a restore, or a
+ * hand edit — holds rows whose provenance is the other vendor, and for one of those the three
+ * disagreed: a non-null FOREIGN slug went to `revoke` and `deleteAuthConfig` at the wrong
+ * vendor, naming one of its apps by a name that means something else there, while the deletes
+ * beneath matched no row at all. The removal then reported the connector gone, with every
+ * local consent row still standing and the right vendor never asked.
+ *
+ * SO THE ROW'S PROVENANCE DECIDES WHAT IS CLEARED, AND THE CONFIGURED PROVIDER DECIDES ONLY
+ * WHETHER A VENDOR IS ASKED. The local half always runs: the criterion two comments up —
+ * removing an app leaves nobody holding brokered access to it — is about this deployment's own
+ * gate, and it holds whichever broker minted the row. The vendor half is skipped where the
+ * row's broker is not the configured one, because there is no client for it here and no
+ * credential to reach it with; `vendorRevocationRequested` then says `false` on every trail
+ * row, which is exactly what that field is for and the same answer a deployment with no broker
+ * at all gets. An account left standing at a vendor this deployment can no longer reach is not
+ * a thing a removal can end, and saying so is better than asking the wrong vendor to.
+ *
+ * The parse is {@link toolkitOfFor} with the ROW's provider rather than `accessFor`, because
+ * what this needs is the pair — which broker, and which app at it — and `accessFor` answers
+ * only the second. It is the same answer for the same rows: `accessFor` reads the slug with
+ * the very same per-provider parser, under the same brokered-provenance test, and is asked
+ * with no entry here for the reason the comment above gives.
+ */
+ const rowProvider =
+ existing && brokeredProvenance(existing.provenance)
+ ? existing.provenance
+ : null;
+ const toolkit =
+ existing && rowProvider
+ ? toolkitOfFor(rowProvider, existing.url)
+ : null;
+ /** The broker to ask, which is none at all where the row is the other vendor's. */
+ const rowBroker = rowProvider === brokerProvider ? broker : null;
+
+ if (rowProvider && toolkit) {
/*
* Read before anything is deleted, because the revokes below need the people and the rows
* are where the people are. Sorted, so two removals of the same app revoke in the same
@@ -1034,7 +1155,8 @@ export function serverMethods(context: StoreContext) {
.from(brokeredConnections)
.where(
and(
- eq(brokeredConnections.provider, "composio"),
+ // The row's own broker, not the configured one: see the comment above `rowProvider`.
+ eq(brokeredConnections.provider, rowProvider),
eq(brokeredConnections.app, toolkit),
),
)
@@ -1049,20 +1171,32 @@ export function serverMethods(context: StoreContext) {
* which may still be standing after a switch back that has not retired it yet. Each is
* revoked as the account its row says it is, under the vendor id the row was filed under,
* so the deployment's account is asked for as the deployment's and never as whoever
- * pressed "remove". A person row always carries its user id (the table's holder check),
- * so the fallback to the deployment form below is the shape a corrupt row would need, not
- * a reading anything here relies on.
+ * pressed "remove".
+ *
+ * READ THROUGH {@link asAccountRef}, AND A ROW THAT NAMES NO ACCOUNT STOPS THE REMOVAL
+ * BEFORE ANYTHING IS ASKED OR DELETED. This was a ternary whose else-branch absorbed every
+ * pair it did not recognise into `{ holder: "deployment" }`, and the two readings below —
+ * the revoke and the trail's `owner` — each repeated it. `brokered_connections_holder_check`
+ * makes `person` with a NULL user id unwritable, which is exactly why this is a refusal
+ * rather than a coercion: a state the database says cannot exist, absorbed into the other
+ * holder, is a withdrawal aimed at the DEPLOYMENT's account over a person's row. The
+ * EMPTY STRING is not hypothetical — `IS NOT NULL` does not stop `''` — and the old
+ * reading's `!== null` let it through as a person ref under nobody's id, which reached
+ * `rowBroker.revoke` and was filed as an `owner` of `""`.
+ *
+ * RESOLVED FOR THE WHOLE SET UP FRONT, so the throw lands before the first revoke rather
+ * than part-way through one: a removal that had already withdrawn two accounts at the
+ * vendor and then threw would leave their rows standing and no trail for either.
*/
- const accountOf = (
- connection: (typeof connected)[number],
- ): AccountRef =>
- connection.holder === "person" && connection.userId !== null
- ? {
- holder: "person",
- userId: connection.userId,
- vendorUserId: connection.vendorUserId,
- }
- : { holder: "deployment", vendorUserId: connection.vendorUserId };
+ const accounts = connected.map((connection): AccountRef => {
+ const account = asAccountRef(connection);
+ if (!account) {
+ throw new PluginInvariantError(
+ `${toolkit} holds a connection row whose holder and user id name no account, so the accounts standing for it cannot be ended.`,
+ );
+ }
+ return account;
+ });
/*
* What the broker was actually asked for each of them, kept so the trail below records the
@@ -1070,25 +1204,62 @@ export function serverMethods(context: StoreContext) {
* key has since been unset can still remove the app, and it could not have been calling it
* either way — but nothing was asked of Composio and the row must not claim otherwise.
*/
- const vendorRevocationRequested: boolean[] = [];
- for (const connection of connected) {
- vendorRevocationRequested.push(
- broker
- ? await broker.revoke({ account: accountOf(connection), toolkit })
- : false,
+ const revokeAnswers: boolean[] = [];
+ /*
+ * What the trail may CLAIM from those answers about this app, read before anything is
+ * revoked, as {@link disconnectBrokered} reads its own; see {@link vendorRevocationClaim}.
+ */
+ const claim = await vendorRevocationClaim(toolkit);
+ /*
+ * THE APPS EACH ACCOUNT'S REVOCATION ENDS BESIDES THIS ONE, read BEFORE anything is revoked
+ * and through {@link resolveSiblings}, the one place that answers it. Where a connection is
+ * keyed `(identity, auth provider)` — Arcade — withdrawing it ends every app sharing it, so a
+ * local row left for one of them claims an account the vendor no longer holds. On Composio the
+ * set is empty by construction and nothing below differs from what this method always did.
+ */
+ const endedBesides: { siblings: string[]; unanswered: number }[] = [];
+ /*
+ * ONE AUTH-PROVIDER RESOLUTION PER TOOLKIT FOR THE WHOLE REMOVAL, not one per person.
+ *
+ * `connected` holds a row per PERSON who connected this app, and each `resolveSiblings`
+ * above resolves the asked toolkit plus every app that person holds. Unmemoized that is a
+ * live vendor call per (person × app) on Arcade — a removal on a deployment with two
+ * hundred people holding five apps each is over a thousand serial calls inside one admin
+ * request, which times out partway and leaves accounts revoked at the vendor with every
+ * local row still standing. Memoized, the bound is the number of DISTINCT TOOLKITS across
+ * those rows, whatever the number of people.
+ *
+ * The memo lives exactly as long as this removal and is dropped with it; see
+ * {@link resolveSiblings} for why one call's worth is semantically free.
+ */
+ const authProviders = new Map>();
+ for (const account of accounts) {
+ /*
+ * ASKED OF `rowBroker` AND NOT OF `broker`, both here and at the revoke below.
+ * {@link resolveSiblings} resolves against the CONFIGURED provider's rows and the active
+ * broker's `authProviderOf`, so asking it about a foreign row's app would be the same
+ * wrong-vendor question the revoke was asking. No broker for this row means nothing was
+ * asked, which is the answer the empty set and the `false` beside it already carry.
+ */
+ endedBesides.push(
+ rowBroker
+ ? await resolveSiblings({ toolkit, account }, authProviders)
+ : { siblings: [], unanswered: 0 },
+ );
+ revokeAnswers.push(
+ rowBroker ? await rowBroker.revoke({ account, toolkit }) : false,
);
}
- await database
- .delete(brokeredConnections)
- .where(
- and(
- eq(brokeredConnections.provider, "composio"),
- eq(brokeredConnections.app, toolkit),
- ),
- );
+ await database.delete(brokeredConnections).where(
+ and(
+ // The row's own broker, as the read above.
+ eq(brokeredConnections.provider, rowProvider),
+ eq(brokeredConnections.app, toolkit),
+ ),
+ );
- for (const [index, connection] of connected.entries()) {
+ for (const [index, account] of accounts.entries()) {
await recordAuditEvent(auditStore, {
eventType: "mcp.account_disconnected",
targetType: "mcp_server",
@@ -1117,11 +1288,11 @@ export function serverMethods(context: StoreContext) {
server: toolkit,
// Whose account this was, by the rule {@link disconnectBrokered} names it by: a
// person's own under their id, the deployment's own as the deployment, never as
- // the administrator who removed the app.
+ // the administrator who removed the app. Taken off the SAME `AccountRef` the revoke
+ // above was asked with — read once, by `asAccountRef` — so the trail cannot name a
+ // holder the withdrawal did not use.
owner:
- connection.holder === "person" && connection.userId !== null
- ? connection.userId
- : "deployment",
+ account.holder === "person" ? account.userId : "deployment",
// The same three-way distinction the vault loop above draws, and the same answer: an
// administrator took the whole app away and the person did nothing.
reason: "mcp_server_removed",
@@ -1134,16 +1305,57 @@ export function serverMethods(context: StoreContext) {
* outlives it somewhere else, so a constant here would be worse than none. It says
* "requested" because that is the strongest thing the vendor's answer supports:
* the upstream withdrawal runs as a background job nothing here can poll.
+ *
+ * AND NEVER FOR A KEY: the claim is {@link vendorRevocationClaim}'s, the one
+ * {@link disconnectBrokered} and {@link retireConnectionsFor} make, and for a key app
+ * it is `false` whatever the vendor answered. This row used to pass the raw answer
+ * through for a key (round 2, A5c).
*/
- vendorRevocationRequested:
- vendorRevocationRequested[index] ?? false,
+ ...claim(revokeAnswers[index] ?? false),
},
});
}
+ /*
+ * THE SIBLINGS THE VENDOR ENDED WITH THIS APP, by the three rules written down once in
+ * {@link clearEndedSiblings} — the same call {@link disconnectBrokered} and
+ * {@link clearWithdrawnAccount} make. This loop used to spell rules 2 and 3 itself and so
+ * missed rule 1: a KEY sibling's row was deleted on the strength of an OAuth revoke that
+ * never ended it, leaving a live account at the vendor with no local row to reach it.
+ *
+ * WHAT DIFFERS FROM THOSE CALLERS IS ONLY THE ASKED APP, and it is handled above rather
+ * than here: a removal ends EVERY holder's row for `toolkit` in one delete keyed on the
+ * ROW's provider, where a disconnect ends one account's. So `endedWith` is `toolkit`, whose
+ * rows are already gone, and the helper never clears it. `revoked` is the vendor's raw
+ * answer per account, as `disconnectBrokered` passes its own `ended`.
+ *
+ * It sits after the revokes above for the same reason the asked app's rows do: an exception
+ * from any of them left every row standing. AND IT CAN CLEAR ONLY THE CONFIGURED PROVIDER'S
+ * OWN ROWS, which is what keeps the helper's `accountRow` delete — keyed on `brokerProvider`
+ * — about the same broker as the siblings it deletes: `revoked` true requires `rowBroker`,
+ * and there is no `rowBroker` for a row the other broker minted.
+ */
+ for (const [index, account] of accounts.entries()) {
+ const { siblings, unanswered } = endedBesides[index] ?? {
+ siblings: [],
+ unanswered: 0,
+ };
+ await clearEndedSiblings({
+ siblings,
+ account,
+ endedWith: toolkit,
+ revoked: revokeAnswers[index] ?? false,
+ unanswered,
+ by,
+ reason: "mcp_server_removed",
+ });
+ }
+
// Last of all, for the reason above, and skipped entirely on a deployment with no
- // broker to have made one.
- if (broker) await broker.deleteAuthConfig(toolkit);
+ // broker to have made one — or, for a row of the other broker's, no broker of ITS that
+ // this deployment can reach. Nothing here holds an auth config at a vendor it is not
+ // configured for, and this slug is not a name that vendor's apps go by.
+ if (rowBroker) await rowBroker.deleteAuthConfig(toolkit);
}
const released = await database.transaction(async (transaction) => {
@@ -1662,7 +1874,9 @@ export function serverMethods(context: StoreContext) {
* ONE EXTRA READ FOR THE WHOLE LIST, and none where the deployment has enabled no apps.
*/
const brokeredApps = await brokeredAppRowsAt(
- rows.filter((row) => toolkitOf(row.url) !== null).map((row) => row.url),
+ rows
+ .filter((row) => toolkitOfFor(brokerProvider, row.url) !== null)
+ .map((row) => row.url),
);
/*
@@ -1677,7 +1891,7 @@ export function serverMethods(context: StoreContext) {
.from(brokeredConnections)
.where(
and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.holder, "deployment"),
),
)
@@ -1686,7 +1900,7 @@ export function serverMethods(context: StoreContext) {
return rows.map((row) => {
const entry = catalogueEntry(row.id);
- const toolkit = toolkitOf(row.url);
+ const toolkit = toolkitOfFor(brokerProvider, row.url);
return {
id: row.id,
title: row.title,
@@ -1809,7 +2023,7 @@ export function serverMethods(context: StoreContext) {
.where(eq(mcpServers.id, serverId))
.limit(1);
if (!row) return undefined;
- const toolkit = toolkitOf(row.url);
+ const toolkit = toolkitOfFor(brokerProvider, row.url);
if (!toolkit) return { ...row, accountMode: null };
const app = await brokeredAppRow(toolkit);
return {
@@ -1840,7 +2054,20 @@ export function serverMethods(context: StoreContext) {
return rows.map((row) => row.url);
},
- /** Every account any holder has on a brokered app, as the broker would be asked to end it. */
+ /**
+ * Every account any holder has on a brokered app, as the broker would be asked to end it.
+ *
+ * READ THROUGH {@link asAccountRef}, AND A ROW THAT NAMES NO ACCOUNT STOPS THE READ. Every
+ * account handed back here is passed straight to {@link disconnectBrokered} by the mode
+ * switch, so what is minted here is what a revoke is aimed at and what the trail records. The
+ * reading this replaces was `userId: row.userId ?? ""`, the quiet direction of the same
+ * coercion `asAccountRef` exists to refuse: a person row carrying no user id came out as a
+ * person ref belonging to NOBODY and was withdrawn under an owner of `""`. `''` is the pair
+ * that is actually reachable — `brokered_connections_holder_check` tests `IS NOT NULL`, which
+ * `''` satisfies — and `null` is unwritable, which is why both are a refusal rather than a
+ * default. The `null` answer below still means only "not a brokered app at this broker";
+ * a row that cannot be read is this deployment contradicting its own CHECK, and says so.
+ */
async accountsOn(
serverId: string,
): Promise<{ toolkit: string; accounts: AccountRef[] } | null> {
@@ -1849,7 +2076,7 @@ export function serverMethods(context: StoreContext) {
.from(mcpServers)
.where(eq(mcpServers.id, serverId))
.limit(1);
- const toolkit = row ? toolkitOf(row.url) : null;
+ const toolkit = row ? toolkitOfFor(brokerProvider, row.url) : null;
if (!toolkit) return null;
const held = await database
.select({
@@ -1860,22 +2087,21 @@ export function serverMethods(context: StoreContext) {
.from(brokeredConnections)
.where(
and(
- eq(brokeredConnections.provider, "composio"),
+ eq(brokeredConnections.provider, brokerProvider),
eq(brokeredConnections.app, toolkit),
),
);
return {
toolkit,
- accounts: held.map(
- (row): AccountRef =>
- row.holder === "person"
- ? {
- holder: "person",
- userId: row.userId ?? "",
- vendorUserId: row.vendorUserId,
- }
- : { holder: "deployment", vendorUserId: row.vendorUserId },
- ),
+ accounts: held.map((row): AccountRef => {
+ const account = asAccountRef(row);
+ if (!account) {
+ throw new PluginInvariantError(
+ `${toolkit} holds a connection row whose holder and user id name no account, so the accounts standing for it cannot be listed.`,
+ );
+ }
+ return account;
+ }),
};
},
@@ -1898,7 +2124,9 @@ export function serverMethods(context: StoreContext) {
.from(mcpServers)
.where(eq(mcpServers.id, serverId))
.limit(1);
- const toolkit = dialled ? toolkitOf(dialled.url) : null;
+ const toolkit = dialled
+ ? toolkitOfFor(brokerProvider, dialled.url)
+ : null;
const app = toolkit ? await brokeredAppRow(toolkit) : null;
await database
.update(mcpServers)
diff --git a/server/src/plugins/plugin-store/skills.ts b/server/src/plugins/plugin-store/skills.ts
index 39ca3dec5..6fae1ce94 100644
--- a/server/src/plugins/plugin-store/skills.ts
+++ b/server/src/plugins/plugin-store/skills.ts
@@ -4,6 +4,7 @@ import {
agentProfiles,
agents,
mcpServers,
+ mcpTools,
pluginGrants,
skills,
skillTools,
@@ -126,6 +127,41 @@ export function skillMethods(context: StoreContext) {
return row !== undefined;
},
+ /**
+ * Whether this server publishes an action named EXACTLY this.
+ *
+ * One indexed read on `mcp_tools`, and deliberately not `listServers`, which materialises every
+ * tool and every grant in the deployment to answer. The comparison is `=` on the stored text and
+ * so CASE-SENSITIVE: Arcade names are dotted and case-sensitive and are never normalized, and
+ * `Gmail.SendEmail` is not `gmail.sendemail`.
+ *
+ * ASKED OF `vendor_name`, WHICH IS THE NAME THE QUESTION IS ABOUT. Every caller of this reaches
+ * it holding an action out of the per-action authorization flow, and that flow speaks the
+ * vendor's spelling from end to end: `grantsFor` lists what Arcade reports, the Authorize button
+ * sends that back, the attempt row stores it, `?action=` carries it home and `authorize({
+ * action })` is refused by the vendor under any other spelling. Asking `name` — this
+ * deployment's own identifier, which for Arcade is the qualified name with its toolkit removed —
+ * would make this gate answer false for every action that exists, and `/servers/:id/authorize`
+ * would refuse the whole flow with "does not publish an action by that name".
+ *
+ * The two columns hold the same string for every other transport, so no other provider's gate
+ * moves. See {@link mcpTools.vendorName}.
+ */
+ async serverPublishesAction(
+ serverId: string,
+ action: string,
+ ): Promise {
+ if (!serverId || !action) return false;
+ const [row] = await database
+ .select({ name: mcpTools.vendorName })
+ .from(mcpTools)
+ .where(
+ and(eq(mcpTools.serverId, serverId), eq(mcpTools.vendorName, action)),
+ )
+ .limit(1);
+ return row !== undefined;
+ },
+
async agentOwner(agentId: string): Promise {
const [row] = await database
.select({ ownerUserId: agentProfiles.ownerUserId })
@@ -186,12 +222,26 @@ export function skillMethods(context: StoreContext) {
})
// Editing keeps the owner it already had. Whose a skill is, is not something a re-save
// should quietly change, and the route has already checked this person may edit it.
+ // `installed_by` is left alone too: it records who first installed it, not who last saved.
.onConflictDoUpdate({
target: skills.slug,
set: {
title: input.title,
summary: input.summary,
instructions: input.instructions,
+ /*
+ * WHERE THE TEXT NOW COMES FROM, WRITTEN BESIDE THE TEXT.
+ *
+ * CRITERION. A save through here leaves `origin` saying what it says on an insert.
+ *
+ * REASON. `origin = 'catalogue'` is the condition the package seed (`tenant-package.ts`,
+ * `setWhere`) overwrites a skill on at every boot. Leaving it standing after somebody
+ * edited a shipped skill here kept the row eligible, so the next boot silently replaced
+ * their edit with the package's text — the exact outcome that seed's own comment says
+ * it exists to prevent ("rather than the package silently replacing something somebody
+ * wrote").
+ */
+ origin: input.origin ?? "yours",
updatedAt: new Date(),
},
});
diff --git a/server/src/plugins/provider-wiring.ts b/server/src/plugins/provider-wiring.ts
new file mode 100644
index 000000000..0da576033
--- /dev/null
+++ b/server/src/plugins/provider-wiring.ts
@@ -0,0 +1,186 @@
+import type { ArcadeActions } from "./arcade";
+import type { ComposioActions } from "./composio";
+import {
+ providerDecision,
+ type ConnectedAppBroker,
+ type ProviderDecision,
+} from "./broker";
+import { BROKERED_PROFILE, type BrokerProvider } from "./access";
+
+/**
+ * The entrypoint's provider decision, with the vendor constructors passed in so this file imports
+ * no vendor package and a test can answer without booting the server or making a request.
+ */
+type ClientFactory = (apiKey: string) => {
+ actions: Actions;
+ broker: ConnectedAppBroker;
+};
+
+export type ProviderWiring = {
+ /** The one active provider, or `null` for a deployment that legitimately has no broker. */
+ provider: BrokerProvider | null;
+ /** The one broker handed to the store and the routes; `null` exactly when `provider` is. */
+ broker: ConnectedAppBroker | null;
+ /** Both transport seams, always both: the one that is not active is explicitly `null`. */
+ install: { composio: ComposioActions | null; arcade: ArcadeActions | null };
+};
+
+/**
+ * Chooses ONE provider from configuration and builds only that provider's client.
+ *
+ * {@link providerDecision} answers one of four kinds, and this function has exactly one outcome for
+ * each, which is the point: the three causes of "no provider" were a single `null` before, and
+ * every caller that then wrote `?? "composio"` turned the one cause that can hold Arcade rows into
+ * "Composio is active".
+ *
+ * `active` wires that provider. `unconfigured` — no key at all — is the ONLY one wired to nothing:
+ * nobody configured a broker, so having none is the answer the configuration asked for.
+ *
+ * `misnamed` AND `ambiguous` ARE REFUSED RATHER THAN DEGRADED. A named provider whose key is unset
+ * or blank is an operator typo. Both keys set with nothing named is an operator half-way through a
+ * sunset who has not said which vendor is live — not a state anybody wants on purpose, because the
+ * deployment comes up holding two vendor keys and brokering nothing, so every brokered feature is
+ * dead. Returning null wiring for either would be a failure nobody is told about at boot: the
+ * symptom surfaces much later and far away, as features that simply do not work, and sends an
+ * operator looking for a broken vendor instead of at their own environment. Both are refused here,
+ * naming the variables and never a value.
+ *
+ * BOTH-KEYS IS COUNTED WITH `credential`, inside {@link providerDecision}, not with
+ * `!== undefined`. A deployment holding two BLANK keys has configured no vendor at all, and must be
+ * told that rather than that both of its vendors are set.
+ *
+ * NO BRANCH BELOW REACHES NULL WIRING BY FALLING THROUGH. The one null wiring is returned from the
+ * branch that means it, and a selected provider whose key is somehow absent throws rather than
+ * quietly becoming the same thing — see the invariant below.
+ */
+export function selectProviderWiring(
+ c: {
+ composioApiKey?: string;
+ arcadeApiKey?: string;
+ brokerProvider?: BrokerProvider;
+ },
+ create: {
+ composio: ClientFactory;
+ arcade: ClientFactory;
+ },
+): ProviderWiring {
+ const decision = providerDecision(c);
+ if (decision.kind === "misnamed") {
+ const keyName = BROKERED_PROFILE[decision.named].envVar;
+ throw new Error(
+ `OPENBOT_BROKER_PROVIDER=${decision.named} names a provider whose ${keyName} is not set to a key. Set ${keyName} to this deployment's key, or change or unset OPENBOT_BROKER_PROVIDER.`,
+ );
+ }
+ if (decision.kind === "ambiguous") {
+ throw new Error(
+ "COMPOSIO_API_KEY and ARCADE_API_KEY are both set and OPENBOT_BROKER_PROVIDER is unset, so no provider is active and nothing brokered would work. Set OPENBOT_BROKER_PROVIDER to composio or arcade to say which vendor is live, or unset the key of the vendor this deployment is not using.",
+ );
+ }
+ if (decision.kind === "unconfigured") {
+ // Neither key set: a deployment that asked for no broker, and gets exactly that.
+ return {
+ provider: null,
+ broker: null,
+ install: { composio: null, arcade: null },
+ };
+ }
+ const provider = decision.provider;
+ if (provider === "arcade") {
+ const client = create.arcade(
+ requiredKey(provider, c.arcadeApiKey, "ARCADE_API_KEY"),
+ );
+ return {
+ provider,
+ broker: client.broker,
+ install: { composio: null, arcade: client.actions },
+ };
+ }
+ const client = create.composio(
+ requiredKey(provider, c.composioApiKey, "COMPOSIO_API_KEY"),
+ );
+ return {
+ provider,
+ broker: client.broker,
+ install: { composio: client.actions, arcade: null },
+ };
+}
+
+/**
+ * The key a selected provider must have, demanded rather than tested.
+ *
+ * {@link providerDecision} selects a provider only from a key it has seen, so this cannot fire today.
+ * It is written as a demand because of what the alternative WAS: the two branches above each
+ * carried `&& c.ApiKey !== undefined`, which made a selected provider with no key fall
+ * through to null wiring — the broker silently unwired, by the very code whose job is to wire it.
+ * A narrowing that doubles as a silent exit is the shape this finding is about, so the narrowing is
+ * kept and the exit is removed: if the two ever disagree, the deployment says so at boot.
+ */
+function requiredKey(
+ provider: BrokerProvider,
+ apiKey: string | undefined,
+ keyName: string,
+): string {
+ if (apiKey !== undefined) return apiKey;
+ throw new Error(
+ `${provider} was selected as the broker but ${keyName} is not set. This is a bug in provider selection, not a configuration error; no broker was wired.`,
+ );
+}
+
+/**
+ * WHICH SLICE of `brokered_connections` this deployment's stores read and write, which is a
+ * different question from which broker is active, and has an answer where that one has none.
+ *
+ * CONFIGURATION FIRST, THEN THE ROWS, AND NEVER A VENDOR PICKED OUT OF THE AIR. An active provider
+ * is the answer. Where configuration names none — a broker key withdrawn after rows were made —
+ * the rows this deployment actually holds name it instead. That is the whole fix: `?? "composio"`
+ * answered "Composio" for a deployment whose every connection is an Arcade one, so those rows were
+ * filtered out of every read, parsed with the wrong parser, and the live accounts behind them could
+ * be neither seen nor revoked. Asking the rows cannot be wrong about them, because they are what is
+ * being named.
+ *
+ * AMBIGUOUS CONFIGURATION NEVER REACHES HERE AT BOOT. {@link selectProviderWiring} refuses both
+ * keys set with nothing named before any store is built, so the `ambiguous` arm below is reachable
+ * only by a direct caller (a test). What DOES reach here is `unconfigured` — no key at all — where
+ * rows can still outlive a withdrawn key and must be read as their own provider's.
+ *
+ * ROWS AT BOTH VENDORS WITH NOTHING ACTIVE IS REFUSED, which is {@link assertActivatable}'s refusal
+ * from the other side: that one stops a deployment activating one provider while the other holds
+ * rows, and this one stops a deployment reading one vendor's rows as the other's while NOTHING is
+ * active. Both say the same thing to an operator — name a provider, and end the other's connections
+ * through its own sunset flow.
+ *
+ * AND NO ROWS AT ALL NAMES AN EMPTY SLICE. A `BrokerProvider` has to be spelled, and with no row of
+ * either vendor there is no row to misfile: every `eq(provider, …)` selects nothing whichever name
+ * is used, and no row can appear while this holds, because writing one needs a live broker and
+ * there is none. The name is a label on an empty set, not a claim that Composio is active — what
+ * answers that is `providerWiring.broker`, which is `null` here.
+ */
+export function storedProvider(
+ decision: ProviderDecision,
+ rowOwners: BrokerProvider[],
+): BrokerProvider {
+ if (decision.kind === "active") return decision.provider;
+ if (decision.kind === "misnamed") {
+ // Unreachable: `selectProviderWiring` stops the server on this before any store is built.
+ throw new Error(
+ `OPENBOT_BROKER_PROVIDER=${decision.named} names a provider whose ${BROKERED_PROFILE[decision.named].envVar} is not set.`,
+ );
+ }
+ const owners = [...new Set(rowOwners)];
+ if (owners.length > 1) {
+ const counted = owners
+ .map((owner) => BROKERED_PROFILE[owner].vendor)
+ .join(" and ");
+ throw new Error(
+ `This deployment holds brokered connections at ${counted}, and nothing names which provider is active: ${
+ decision.kind === "ambiguous"
+ ? `${decision.configured.map((p) => BROKERED_PROFILE[p].envVar).join(" and ")} are both set and OPENBOT_BROKER_PROVIDER is unset`
+ : "no broker API key is set"
+ }. Set OPENBOT_BROKER_PROVIDER to the provider those connections are to be read as, and end the other provider's connections through its own sunset flow.`,
+ );
+ }
+ const owner = owners[0];
+ if (owner !== undefined) return owner;
+ // No rows of either vendor. See the last paragraph above: this labels an empty set.
+ return "composio";
+}
diff --git a/server/src/plugins/route-groups/composio-apps.ts b/server/src/plugins/route-groups/composio-apps.ts
index 5c23a242b..ca7c4ead9 100644
--- a/server/src/plugins/route-groups/composio-apps.ts
+++ b/server/src/plugins/route-groups/composio-apps.ts
@@ -1,11 +1,12 @@
import { requireAdmin } from "../../auth/guards";
import { reasonWithoutStatement } from "../../db/query-failure";
+import { BROKERED_PROFILE } from "../access";
import {
type BrokerApp,
BrokerUnconfiguredError,
brokerSentence,
} from "../broker";
-import { toolkitOf, vendorSentence } from "../composio";
+import { vendorSentence } from "../composio";
import {
CatalogueEntryUnknownError,
CustomServerRefusedError,
@@ -15,8 +16,9 @@ import {
} from "../store";
import {
brokerRefusal,
- DIRECTORY_UNAVAILABLE,
+ directoryUnavailable,
type PluginRouteContext,
+ withVendorDeadline,
} from "./context";
/** Browsing Composio's directory and enabling an app out of it. */
@@ -25,6 +27,9 @@ export function mountComposioAppRoutes({
store,
requireUser,
composio,
+ activeProvider,
+ activeVendor,
+ appOfUrl,
actorEmail,
}: PluginRouteContext) {
/**
@@ -44,8 +49,9 @@ export function mountComposioAppRoutes({
* {@link BrokerUnconfiguredError}'s own message is what is sent rather than a sentence written
* here.
*
- * `enabled` comes off `toolkitOf(url)` and never off the row's id. The url is where the
- * transport reads which app a call is against, so it is the only reading that decides anything;
+ * `enabled` comes off `appOfUrl(url)` — the active broker's own parser — and never off the
+ * row's id. The url is where the transport reads which app a call is against, so it is the only
+ * reading that decides anything;
* the id names the row — `composio-linear` — and reading one as the other would quietly work
* until somebody renamed a row. `serverUrls` hands over the urls and nothing else, which is that
* property made structural: there is no id here to read by mistake.
@@ -61,18 +67,24 @@ export function mountComposioAppRoutes({
if (!composio) {
return context.json(
- { error: new BrokerUnconfiguredError().message },
+ {
+ error: new BrokerUnconfiguredError(activeProvider, "operator")
+ .message,
+ },
503,
);
}
let directory: BrokerApp[];
try {
- directory = await composio.broker.listApps();
+ directory = await withVendorDeadline(() => composio.broker.listApps());
} catch (error) {
// The vendor's own sentence where there is one, because "invalid api key" is the diagnosis
// and a 500 is not. See {@link brokerRefusal} for what is kept out of the answer.
- const refusal = brokerRefusal(error, DIRECTORY_UNAVAILABLE);
+ const refusal = brokerRefusal(
+ error,
+ directoryUnavailable(activeProvider),
+ );
return context.json({ error: refusal.error }, refusal.status);
}
/*
@@ -121,7 +133,7 @@ export function mountComposioAppRoutes({
const enabled = new Set(
urls
- .map((url) => toolkitOf(url))
+ .map((url) => appOfUrl(url))
.filter((toolkit): toolkit is string => toolkit !== null),
);
return context.json({
@@ -148,7 +160,10 @@ export function mountComposioAppRoutes({
if (!composio) {
return context.json(
- { error: new BrokerUnconfiguredError().message },
+ {
+ error: new BrokerUnconfiguredError(activeProvider, "operator")
+ .message,
+ },
503,
);
}
@@ -171,7 +186,7 @@ export function mountComposioAppRoutes({
let directory: BrokerApp[] = [];
if (slug) {
try {
- directory = await composio.broker.listApps();
+ directory = await withVendorDeadline(() => composio.broker.listApps());
} catch (error) {
/*
* The same failure the directory route answers, in the same words, because it is the same
@@ -179,7 +194,10 @@ export function mountComposioAppRoutes({
* deployment whose key is wrong was told nothing at all, on the one screen where the key
* had just been set.
*/
- const refusal = brokerRefusal(error, DIRECTORY_UNAVAILABLE);
+ const refusal = brokerRefusal(
+ error,
+ directoryUnavailable(activeProvider),
+ );
return context.json({ error: refusal.error }, refusal.status);
}
}
@@ -188,7 +206,7 @@ export function mountComposioAppRoutes({
return context.json(
{
error: slug
- ? `${slug} is not an app Composio lists for this deployment.`
+ ? `${slug} is not an app ${BROKERED_PROFILE[activeProvider].vendor} lists for this deployment.`
: "An app is required.",
},
400,
@@ -325,7 +343,7 @@ export function mountComposioAppRoutes({
JSON.stringify({
type: "composio-app-not-enabled",
slug: app.slug,
- note: "Enabling a brokered app failed for a reason neither this deployment nor Composio put a sentence to. The administrator was answered 502 with the generic sentence.",
+ note: `Enabling a brokered app failed for a reason neither this deployment nor ${activeVendor} put a sentence to. The administrator was answered 502 with the generic sentence.`,
error: reasonWithoutStatement(error),
}),
);
@@ -333,7 +351,7 @@ export function mountComposioAppRoutes({
const refusal = brokerRefusal(
error,
- `${app.name} could not be enabled, and Composio said nothing about why. Try again, and check this deployment's Composio key if it persists.`,
+ `${app.name} could not be enabled, and ${activeVendor} said nothing about why. Try again, and check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
diff --git a/server/src/plugins/route-groups/connections.ts b/server/src/plugins/route-groups/connections.ts
index 7ef106608..2c161ea71 100644
--- a/server/src/plugins/route-groups/connections.ts
+++ b/server/src/plugins/route-groups/connections.ts
@@ -1,15 +1,18 @@
import type { AppVariables } from "../../auth/guards";
+import { reasonWithoutStatement } from "../../db/query-failure";
+import { normalizeArcadeError } from "../arcade-adapter";
import {
type BrokerField,
BrokerUnconfiguredError,
brokerReturnUrl,
+ brokerSentence,
type Decides,
+ type GrantState,
isFieldScheme,
type SchemeKind,
schemeKind,
} from "../broker";
import { catalogueEntry } from "../catalogue";
-import { toolkitOf } from "../composio";
import {
authorizationUrlFor,
type ConnectOrigin,
@@ -19,13 +22,23 @@ import {
redirectUriFor,
sealConnectState,
} from "../oauth";
-import type { AccountRef } from "../shared-accounts";
+import { type AccountRef, asAccountRef } from "../shared-accounts";
import {
CatalogueEntryUnknownError,
type OAuthClient,
PluginRefusedError,
} from "../store";
-import { brokerRefusal, type PluginRouteContext } from "./context";
+import {
+ type AuthorizationRefusal,
+ brokerRefusal,
+ logReturnUrlRefusal,
+ type PluginRouteContext,
+ STATUS_CALL_TIMEOUT_MS,
+ sameAccount,
+ settledStatus,
+ signInUrlFor,
+ withVendorDeadline,
+} from "./context";
/** A person's connections: listing them, connecting an app (OAuth or brokered) and disconnecting. */
export function mountConnectionRoutes({
@@ -34,6 +47,9 @@ export function mountConnectionRoutes({
requireUser,
connect,
composio,
+ activeProvider,
+ activeVendor,
+ appOfUrl,
actorEmail,
}: PluginRouteContext) {
/**
@@ -135,7 +151,7 @@ export function mountConnectionRoutes({
* person. On a deployment with no `OPENBOT_PUBLIC_URL` it failed one step earlier still,
* refusing for want of a setting that has no bearing on a flow it does not enter.
*
- * The app comes off the row's url via `toolkitOf` rather than off its id, for the reason the
+ * The app comes off the row's url via `appOfUrl` rather than off its id, for the reason the
* directory route says: the url is where the transport reads which app a call is against, and
* the id is a row name that happens to look similar.
*
@@ -146,7 +162,7 @@ export function mountConnectionRoutes({
* a missing row did when this was a `.find`.
*/
const row = await store.serverAddress(serverId);
- const toolkit = row ? toolkitOf(row.url) : null;
+ const toolkit = row ? appOfUrl(row.url) : null;
if (row && toolkit) {
/*
* AN APP THAT NEEDS NO CREDENTIAL IS ANSWERED HERE, BEFORE `brokeredAccountFor` IS EVER
@@ -200,7 +216,10 @@ export function mountConnectionRoutes({
*/
if (!composio) {
return context.json(
- { error: new BrokerUnconfiguredError().message },
+ {
+ error: new BrokerUnconfiguredError(activeProvider, "person")
+ .message,
+ },
503,
);
}
@@ -336,14 +355,16 @@ export function mountConnectionRoutes({
*/
let published: BrokerField[];
try {
- published = await composio.broker.connectionFields({
- toolkit,
- authScheme,
- });
+ published = await withVendorDeadline(() =>
+ composio.broker.connectionFields({
+ toolkit,
+ authScheme,
+ }),
+ );
} catch (error) {
const refusal = brokerRefusal(
error,
- `Composio would not say what ${row.title} asks for, and said nothing about why. Try again, and ask an administrator to check this deployment's Composio key if it persists.`,
+ `${activeVendor} would not say what ${row.title} asks for, and said nothing about why. Try again, and ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
@@ -373,7 +394,7 @@ export function mountConnectionRoutes({
if (published.length === 0) {
return context.json(
{
- error: `${row.title} publishes no boxes to fill in, so there is nothing you could type that would reach it and no account was made. An administrator has to look at how ${row.title} is set up at Composio; removing it on the Plugins page and adding it again is what records the way Composio connects it now.`,
+ error: `${row.title} publishes no boxes to fill in, so there is nothing you could type that would reach it and no account was made. An administrator has to look at how ${row.title} is set up at ${activeVendor}; removing it on the Plugins page and adding it again is what records the way ${activeVendor} connects it now.`,
},
400,
);
@@ -451,7 +472,7 @@ export function mountConnectionRoutes({
const optional = published.map((field) => field.name).join(", ");
return context.json(
{
- error: `${row.title} publishes no box it says has to be filled in — only ${optional} — so nothing you could type would be a credential it insists on, and no account was made. An app that genuinely needs none is one Composio publishes as needing no authentication, and ${row.title} is recorded here as an app whose secret you type. An administrator has to look at how ${row.title} is set up at Composio; removing it on the Plugins page and adding it again is what records the way Composio connects it now.`,
+ error: `${row.title} publishes no box it says has to be filled in — only ${optional} — so nothing you could type would be a credential it insists on, and no account was made. An app that genuinely needs none is one ${activeVendor} publishes as needing no authentication, and ${row.title} is recorded here as an app whose secret you type. An administrator has to look at how ${row.title} is set up at ${activeVendor}; removing it on the Plugins page and adding it again is what records the way ${activeVendor} connects it now.`,
},
400,
);
@@ -517,7 +538,7 @@ export function mountConnectionRoutes({
*/
return context.json(
{
- error: `That is not the form ${row.title} publishes, so nothing was sent to Composio. Press Connect again to draw it from what the app asks for now, and fill in the boxes it shows; each one holds text.`,
+ error: `That is not the form ${row.title} publishes, so nothing was sent to ${activeVendor}. Press Connect again to draw it from what the app asks for now, and fill in the boxes it shows; each one holds text.`,
},
400,
);
@@ -565,7 +586,7 @@ export function mountConnectionRoutes({
if (missing.length > 0) {
return context.json(
{
- error: `${row.title} cannot be connected without ${missing.join(", ")}, so nothing was sent to Composio. Fill in every box the form marks required — a box holding only spaces is an empty one — and press Connect again.`,
+ error: `${row.title} cannot be connected without ${missing.join(", ")}, so nothing was sent to ${activeVendor}. Fill in every box the form marks required — a box holding only spaces is an empty one — and press Connect again.`,
},
400,
);
@@ -606,7 +627,7 @@ export function mountConnectionRoutes({
}
const refusal = brokerRefusal(
error,
- `${row.title} could not be connected with what you entered, and Composio said nothing about why. Try again, and ask an administrator to check this deployment's Composio key if it persists.`,
+ `${row.title} could not be connected with what you entered, and ${activeVendor} said nothing about why. Try again, and ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
@@ -633,7 +654,7 @@ export function mountConnectionRoutes({
if (kind === "unreadable") {
return context.json(
{
- error: `This deployment cannot tell how ${row.title} connects, so nothing was sent to Composio and no account was made. An administrator has to remove it on the Plugins page and add it again, which records the way Composio connects it now.`,
+ error: `This deployment cannot tell how ${row.title} connects, so nothing was sent to ${activeVendor} and no account was made. An administrator has to remove it on the Plugins page and add it again, which records the way ${activeVendor} connects it now.`,
},
400,
);
@@ -667,8 +688,7 @@ export function mountConnectionRoutes({
if (!connect?.appUrl) {
return context.json(
{
- error:
- "This deployment has no app URL configured, so Composio would have nowhere to send you back to. Set OPENBOT_APP_URL.",
+ error: `This deployment has no app URL configured, so ${activeVendor} would have nowhere to send you back to. Set OPENBOT_APP_URL.`,
},
503,
);
@@ -712,26 +732,35 @@ export function mountConnectionRoutes({
*/
let redirectUrl: string;
try {
- ({ redirectUrl } = await composio.broker.authorize({
- account,
- toolkit,
- /*
- * THE REFUSAL ABOVE CHECKS THAT A SETTING IS SET; THIS CHECKS THAT IT IS AN ADDRESS.
- * `appUrl` is an environment string — `OPENBOT_APP_URL`, or the first `TRUSTED_ORIGINS`
- * entry — and nothing between there and Composio has ever looked at it, so
- * `openbot.example.com` with the scheme left off builds a callback that is not a
- * callback. That failure lands after somebody has consented, on the vendor's page, where
- * this deployment cannot tell them anything; the guard moves it to before the link is
- * minted, where the sentence reaches an operator who can set the variable.
- */
- returnUrl: brokerReturnUrl(
- connectedAccountsUrlFor(connect.appUrl, { serverId }, returnTo),
- ),
- }));
+ ({ redirectUrl } = await withVendorDeadline(() =>
+ composio.broker.authorize({
+ account,
+ toolkit,
+ /*
+ * THE REFUSAL ABOVE CHECKS THAT A SETTING IS SET; THIS CHECKS THAT IT IS AN ADDRESS.
+ * `appUrl` is an environment string — `OPENBOT_APP_URL`, or the first `TRUSTED_ORIGINS`
+ * entry — and nothing between there and Composio has ever looked at it, so
+ * `openbot.example.com` with the scheme left off builds a callback that is not a
+ * callback. That failure lands after somebody has consented, on the vendor's page, where
+ * this deployment cannot tell them anything; the guard moves it to before the link is
+ * minted, where nobody has spent a consent on it yet.
+ *
+ * WHO IS READING DECIDES WHAT IS SAID, AND HERE THAT IS A PERSON. This route is
+ * `requireUser`: whoever pressed Connect gets the refusal's own sentence, which names no
+ * vendor and no variable because they can act on neither. The operator's half of it is
+ * logged below instead, where an operator looks and a person does not.
+ */
+ returnUrl: brokerReturnUrl(
+ connectedAccountsUrlFor(connect.appUrl, { serverId }, returnTo),
+ activeProvider,
+ ),
+ }),
+ ));
} catch (error) {
+ logReturnUrlRefusal(error, serverId);
const refusal = brokerRefusal(
error,
- `Composio would not begin a connection to ${row.title}, and said nothing about why. Try again, and ask an administrator to check this deployment's Composio key if it persists.`,
+ `${activeVendor} would not begin a connection to ${row.title}, and said nothing about why. Try again, and ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
@@ -851,7 +880,7 @@ export function mountConnectionRoutes({
*
* A ROW THAT IS NOT BROKERED IS REFUSED IN SO MANY WORDS. The id may well name a server this
* deployment really has — what is wrong is that its connection does not live at Composio, and
- * there is nothing for either route to confirm or to end. `null` from `toolkitOf` also covers an
+ * there is nothing for either route to confirm or to end. `null` from `appOfUrl` also covers an
* id naming no row at all, which is the same answer from the caller's side.
*
* NO BROKER IS A 503 NAMING THE SETTING, as it is on the directory and on connect, and it is
@@ -864,7 +893,7 @@ export function mountConnectionRoutes({
* actor who is not an administrator is refused here, once, instead of in four routes below.
*
* THE CONNECT ROUTE'S BROKERED BRANCH IS THE FOURTH, CALLED ONLY ONCE IT ALREADY KNOWS THE
- * BRANCH IS TAKEN. That route still asks `toolkitOf` itself before this function is ever reached
+ * BRANCH IS TAKEN. That route still asks `appOfUrl` itself before this function is ever reached
* — a row that is not brokered has an OAuth flow below it to fall through to, and refusing it
* here, where there is no such flow to fall through to, would be wrong. Once that question is
* settled and the answer is yes, whose account the connection is for is exactly the question
@@ -874,6 +903,13 @@ export function mountConnectionRoutes({
const brokeredAccountFor = async (
context: { var: AppVariables },
serverId: string,
+ /**
+ * `readOnly` is true for a question about the account's STATE, which anybody working with the
+ * app benefits from, and false for every act done in it. It lifts exactly one refusal — the
+ * administrator requirement on a Shared app's account — and nothing else: the account
+ * resolved is the same one either way, so a reader is told about the identity calls use.
+ */
+ options: { readOnly?: boolean } = {},
): Promise<
| {
toolkit: string;
@@ -889,7 +925,7 @@ export function mountConnectionRoutes({
}
> => {
const row = await store.serverAddress(serverId);
- const toolkit = row ? toolkitOf(row.url) : null;
+ const toolkit = row ? appOfUrl(row.url) : null;
if (!toolkit) {
return {
refusal: {
@@ -900,7 +936,10 @@ export function mountConnectionRoutes({
}
if (!composio) {
return {
- refusal: { error: new BrokerUnconfiguredError().message, status: 503 },
+ refusal: {
+ error: new BrokerUnconfiguredError(activeProvider, "person").message,
+ status: 503,
+ },
};
}
const resolved = await store.accountRefFor(serverId, context.var.actor.id);
@@ -908,6 +947,7 @@ export function mountConnectionRoutes({
return { refusal: { error: resolved.refusal, status: 400 } };
}
if (
+ !options.readOnly &&
resolved.ref.holder === "deployment" &&
context.var.actor.role !== "admin"
) {
@@ -921,6 +961,69 @@ export function mountConnectionRoutes({
return { toolkit, account: resolved.ref, title: resolved.title };
};
+ /**
+ * What the account behind this app holds, action by action: `{ actions: GrantState[] }`.
+ *
+ * A BOOLEAN WOULD NOT DO. An action can be blocked by consent, which Authorize moves, or by a
+ * deployment secret an operator sets once for everybody, which no amount of consenting moves; and
+ * a listing nobody could read is `unknown` rather than a pessimistic `needs-consent`, because
+ * telling somebody to authorize what they already authorized sends them round twice. The states
+ * are the broker's, passed through as it answered them.
+ *
+ * WHOSE IDENTITY IS ASKED ABOUT COMES FROM `brokeredAccountFor`: a Personal app's is the
+ * viewer's, a Shared app's is the app's own `shared_vendor_user_id`. Asking about the viewer for
+ * a Shared app would report a state no call ever uses.
+ *
+ * BEHIND `requireUser` AND NOT ADMIN-GATED: the status is a fact about the team's account that
+ * anybody working with the app benefits from. Minting is the privileged act, and the authorize
+ * route still refuses it with 403 through the same resolver.
+ *
+ * NO PRESENCE TEST ON THE BROKER AND NO REFUSAL PATH: every broker answers `grantsFor`, a
+ * degenerate answer being a real one, so this works on both deployments. A throw from the broker
+ * is answered as `unknown` carrying our own sentence, never the vendor's words, which could
+ * carry a credential.
+ */
+ routes.get("/servers/:id/grants", requireUser, async (context) => {
+ const resolved = await brokeredAccountFor(
+ context,
+ context.req.param("id"),
+ { readOnly: true },
+ );
+ if (resolved.refusal) {
+ return context.json(
+ { error: resolved.refusal.error },
+ resolved.refusal.status,
+ );
+ }
+ const { toolkit, account } = resolved;
+ // `composio` is non-null here: the resolver refuses 503 before it returns an account.
+ const broker = composio?.broker;
+ if (!broker) {
+ return context.json(
+ {
+ error: new BrokerUnconfiguredError(activeProvider, "person").message,
+ },
+ 503,
+ );
+ }
+ let actions: GrantState[];
+ try {
+ actions = await withVendorDeadline(() =>
+ broker.grantsFor({ toolkit, account }),
+ );
+ } catch {
+ actions = [
+ {
+ action: toolkit,
+ state: "unknown",
+ reason:
+ "This deployment could not read what this account holds just now, so nothing is said about it either way. Try again in a moment.",
+ },
+ ];
+ }
+ return context.json({ actions });
+ });
+
/**
* Ask Composio whether this person's account is really attached, and write the answer down.
*
@@ -993,7 +1096,7 @@ export function mountConnectionRoutes({
*/
const refusal = brokerRefusal(
error,
- "Composio would not say whether this account is connected, and gave no reason, so what is shown here is the last answer it gave rather than a fresh one. Try again, and ask an administrator to check this deployment's Composio key if it persists.",
+ `${activeVendor} would not say whether this account is connected, and gave no reason, so what is shown here is the last answer it gave rather than a fresh one. Try again, and ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
@@ -1081,7 +1184,7 @@ export function mountConnectionRoutes({
*/
const refusal = brokerRefusal(
error,
- "Composio would not say whether this connection still works, and gave no reason, so what is shown here is the last answer it gave rather than a fresh one. Press Re-check again, and ask an administrator to check this deployment's Composio key if it persists.",
+ `${activeVendor} would not say whether this connection still works, and gave no reason, so what is shown here is the last answer it gave rather than a fresh one. Press Re-check again, and ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
@@ -1148,9 +1251,1216 @@ export function mountConnectionRoutes({
*/
const refusal = brokerRefusal(
error,
- "Composio would not end this account, and gave no reason. Press Disconnect again: the revoke at Composio runs before anything here is deleted, so repeating it is safe and is the whole recovery. Ask an administrator to check this deployment's Composio key if it persists.",
+ `${activeVendor} would not end this account, and gave no reason. Press Disconnect again: the revoke at ${activeVendor} runs before anything here is deleted, so repeating it is safe and is the whole recovery. Ask an administrator to check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
});
+
+ /**
+ * WHAT DISCONNECTING THIS APP WOULD END BESIDES IT, for the confirmation to show before the
+ * press. Answers `{ siblings, unanswered }`: the other apps' toolkits, sorted, and how many apps
+ * on the same vendor identity could not be asked about and so are left out (a count, never
+ * names). It acts on nothing: no revoke, no delete, no audit row.
+ *
+ * THE SET IS THE STORE'S, KEYED ON THE PAIR `(vendor identity, auth provider)` AND NEVER ON THE
+ * PROVIDER ALONE — see `siblingApps`. Nothing is derived here, so this route and the cascade
+ * `DELETE /servers/:id/connection` runs cannot disagree about which apps are named. On Composio
+ * an app is its own auth provider, so the answer is always empty.
+ *
+ * THE ACCOUNT COMES FROM `brokeredAccountFor`, exactly as on the DELETE it previews: the list is
+ * about the account that press would end, so it is asked of the same resolution and is refused
+ * to the same callers.
+ */
+ routes.get(
+ "/servers/:id/connection/siblings",
+ requireUser,
+ async (context) => {
+ const resolved = await brokeredAccountFor(
+ context,
+ context.req.param("id"),
+ );
+ if (resolved.refusal) {
+ return context.json(
+ { error: resolved.refusal.error },
+ resolved.refusal.status,
+ );
+ }
+ try {
+ return context.json(
+ await store.siblingApps({
+ toolkit: resolved.toolkit,
+ account: resolved.account,
+ }),
+ );
+ } catch (error) {
+ const refusal = brokerRefusal(
+ error,
+ "This deployment could not work out which other apps share this connection, and the vendor said nothing about why. Try again.",
+ );
+ return context.json({ error: refusal.error }, refusal.status);
+ }
+ },
+ );
+
+ /**
+ * Begin authorizing ONE ACTION of a brokered app, for a provider that grants action by action.
+ * Answers `{ authorizationUrl }` and nothing else.
+ *
+ * ORDERING PRINCIPLE: EVERYTHING THAT CAN FAIL WITHOUT THE VENDOR RUNS BEFORE THE CLAIM; THE
+ * CLAIM IS THE LAST THING BEFORE THE MINT. A bad `OPENBOT_APP_URL` must not leave a live attempt
+ * with no mint behind it, and the mint runs after the insert so a mint can never exist without a
+ * row to correlate it.
+ *
+ * THE CAPABILITY GATE IS FIRST, BEFORE `brokeredAccountFor` AND BEFORE ANYTHING IS WRITTEN. A
+ * broker that declares no authorization flow cannot complete one, so claiming a row for it would
+ * block the actor for the full attempt lifetime over a flow that was never possible; a gate after
+ * the claim does not prevent that. It is a presence test on a declared capability and never a
+ * vendor name. 503 and not 404 or 400: the route exists, so 404 would lie, and the request is not
+ * what is wrong; 503 is this file's vocabulary for a capability this deployment does not have.
+ *
+ * `actorId` IS THE SESSION SUBJECT (`users.id`) AND NEVER A CREDENTIAL ACTOR: the verifier finds
+ * the attempt from the returning browser's session.
+ *
+ * THE URL IS A BEARER CAPABILITY — answered to this browser and to nothing else. It is never
+ * logged, audited or put in an error body. No `next_uri` is sent: the vendor refuses every value,
+ * and the landing page comes from the attempt row instead.
+ *
+ * A FAILED MINT'S ATTEMPT is handled at the mint's `catch`. NOT HERE: cancelling one.
+ */
+ routes.post("/servers/:id/authorize", requireUser, async (context) => {
+ const serverId = context.req.param("id");
+
+ if (!composio) {
+ return context.json(
+ {
+ error: new BrokerUnconfiguredError(activeProvider, "person").message,
+ },
+ 503,
+ );
+ }
+ const flow = composio.broker.authorizations;
+ if (!flow) {
+ return context.json(
+ {
+ error:
+ "This deployment's apps are connected once for the whole app, with the Connect button on the app's page; they are not authorized one action at a time.",
+ },
+ 503,
+ );
+ }
+
+ const resolved = await brokeredAccountFor(context, serverId);
+ if (resolved.refusal) {
+ return context.json(
+ { error: resolved.refusal.error },
+ resolved.refusal.status,
+ );
+ }
+ const { account, toolkit, title } = resolved;
+
+ const body = await context.req.json().catch(() => null);
+ const action =
+ body && typeof body === "object" && "action" in body
+ ? (body as { action: unknown }).action
+ : undefined;
+ // Exact `===` against the stored names: Arcade's are case-sensitive and never normalized.
+ if (
+ typeof action !== "string" ||
+ !(await store.serverPublishesAction(serverId, action))
+ ) {
+ return context.json(
+ { error: `${title} does not publish an action by that name.` },
+ 400,
+ );
+ }
+
+ if (!connect?.appUrl) {
+ return context.json(
+ {
+ error:
+ "This deployment has no app URL configured, so there is nowhere to send you back to. Set OPENBOT_APP_URL.",
+ },
+ 503,
+ );
+ }
+ const returnTo: ConnectOrigin =
+ context.req.query("returnTo") === "admin" ? "admin" : "settings";
+ let returnUrl: string;
+ try {
+ returnUrl = brokerReturnUrl(
+ connectedAccountsUrlFor(connect.appUrl, { serverId }, returnTo),
+ activeProvider,
+ );
+ } catch (error) {
+ logReturnUrlRefusal(error, serverId);
+ const refusal = brokerRefusal(
+ error,
+ "This deployment could not build the address to send you back to, so nothing was started.",
+ );
+ return context.json({ error: refusal.error }, refusal.status);
+ }
+
+ const claim = await store.claimAuthorizationAttempt({
+ actorId: context.var.actor.id,
+ serverId,
+ action,
+ account,
+ });
+ if (!claim.claimed) {
+ return context.json(
+ {
+ error: claim.live
+ ? `An authorization for ${claim.live.action} is already in progress. Finish it, or wait for it to expire, before starting another.`
+ : "An authorization is already in progress. Finish it, or wait for it to expire, before starting another.",
+ },
+ 409,
+ );
+ }
+
+ let minted: Awaited>;
+ try {
+ minted = await withVendorDeadline(() =>
+ composio.broker.authorize({
+ account,
+ toolkit,
+ action,
+ returnUrl,
+ }),
+ );
+ } catch (error) {
+ /*
+ * WHAT A FAILED MINT DOES TO ITS ATTEMPT. THREE OUTCOMES, KEYED ON WHAT WAS ANSWERED.
+ *
+ * DEFINITE: the broker refused before asking (an authored `BrokerRefusalError`), or the
+ * vendor ANSWERED with an HTTP status. Nothing is live upstream, so the attempt is discarded
+ * and the actor is free to try again. Matched on `(actor_id, created_at)`, never `auth_id`: an
+ * attempt whose mint never completed has none, and `created_at` is what stops a late cleanup
+ * from deleting a NEWER attempt by the same actor.
+ *
+ * UNCERTAIN, AND THE ATTEMPT IS LEFT STANDING: `status === undefined` on a normalized SDK
+ * error. `APIConnectionError` and `APIUserAbortError` EXTEND `APIError`, so the normalizer
+ * answers `{ status: undefined, ... }` for them, NOT `null`; a dropped connection or an
+ * aborted socket means Arcade may well have created a flow. Keying this on `null` would class
+ * a timeout as a refusal and delete an attempt whose flow may be live, which frees the actor
+ * to start a second flow beside it. An attempt holding a null `auth_id` can never complete,
+ * so leaving it to expire is safe, and it is the point.
+ *
+ * THE NORMALIZER ANSWERING `null` IS UNCERTAIN TOO. That is "not an SDK error at all", and
+ * the adapter throws exactly such an `Error` ("Arcade answered an authorization with no id.")
+ * for a mint that answered SUCCESSFULLY without an `ar_...`: Arcade very likely did create a
+ * flow. Do not tidy this case into the definite branch.
+ *
+ * AND {@link VendorTimeoutError} LANDS IN THAT SAME UNCERTAIN BRANCH, which is why the mint's
+ * deadline is safe to add: it is neither authored nor an SDK error, so it is read as "we
+ * stopped waiting and do not know", the attempt is left standing, and a flow the vendor may
+ * have created behind the deadline is not orphaned beside a second one.
+ *
+ * NOTHING FROM THE VENDOR'S ANSWER IS LOGGED, AUDITED OR RETURNED: its body can carry the
+ * authorization url, a bearer capability.
+ */
+ const authored = brokerSentence(error) !== null;
+ const failure = normalizeArcadeError(error);
+ const definite =
+ authored || (failure !== null && failure.status !== undefined);
+ if (definite) {
+ /*
+ * THE CLEAN-UP IS GUARDED, FOR THE SAME REASON THE RECORD-FAILURE CLEAN-UP BELOW IS.
+ *
+ * This discard is a tidy-up for a refusal the handler has ALREADY WORKED OUT — which vendor
+ * sentence to pass on, and whether it is a 502 or an authored 503. Unguarded, a fault on the
+ * delete threw past the handler and the person got a BODYLESS 500 instead: the least
+ * informative answer possible, in exactly the case where this route had something specific
+ * and true to tell them, and the failure of a clean-up destroyed the error it was cleaning
+ * up after. The refusal is the answer; whether the row went with it is not the person's
+ * problem to be given a blank page over.
+ *
+ * WHAT A FAILED DISCARD COSTS THEM IS SAID RATHER THAN GUESSED AT. The attempt stands with a
+ * null `auth_id`, so it can never complete and `claimAuthorizationAttempt` refuses a second
+ * start until it is fifteen minutes old — which makes the generic sentence's "try again in a
+ * moment" false. The extra clause is the same one the record-failure branch answers with for
+ * the same condition, and it is added rather than substituted: the vendor's own sentence is
+ * the half that says WHY, and it is not thrown away to make room for ours.
+ *
+ * The error is read through {@link reasonWithoutStatement}, as every database failure logged
+ * on this route is: a `DrizzleQueryError`'s own message is the statement and every value
+ * bound to it.
+ */
+ let freed = false;
+ try {
+ freed = await store.discardAuthorizationAttempt({
+ actorId: context.var.actor.id,
+ createdAt: claim.attempt.createdAt,
+ });
+ } catch (cleanup) {
+ console.error(
+ JSON.stringify({
+ type: "arcade-refused-attempt-not-cleared",
+ server: serverId,
+ action,
+ note: "A mint was refused and its attempt could not be deleted. The person was told why the vendor refused, and the row holds them until it expires.",
+ error: reasonWithoutStatement(cleanup),
+ }),
+ );
+ freed = false;
+ }
+ const refusal = brokerRefusal(
+ error,
+ "The app's vendor refused to start that authorization, so nothing was started. Try again in a moment.",
+ );
+ return context.json(
+ {
+ error: freed
+ ? refusal.error
+ : `${refusal.error} The attempt could not be cleared either; it expires within fifteen minutes, and starting over works after that.`,
+ },
+ refusal.status,
+ );
+ }
+ /*
+ * THE UNCERTAIN MINT: THE CLAIM STANDS, AND IT DOES NOT STAND IN SILENCE.
+ *
+ * WHY THE CLAIM STANDS. Nobody can read the vendor's answer here, so nobody knows whether a
+ * flow exists at the vendor. Ending the attempt on that guess is the irreversible mistake in
+ * one direction: a deleted claim frees this actor to start a second flow beside one that may
+ * be live. A settled verdict is no better: `closeAuthorizationAttempt` matches on `auth_id`,
+ * which is NULL here, so there is no verdict this row could take, and a settled row frees the
+ * actor just as a deleted one does. Left standing with a NULL `auth_id`, the row can never
+ * complete and holds the actor until it is fifteen minutes old. That cost is bounded. A
+ * second live flow is not.
+ *
+ * WHY IT IS NOT LEFT SILENT. Before this line the branch wrote nothing anywhere. The row says
+ * "a claim with no mint", which is also what a mint still in flight looks like. The person got
+ * a 502 sentence and the deployment kept no trace. So a flow this deployment may have
+ * started, and will never see completed, was invisible to everybody who could end it. This
+ * is B6a's shape one seam earlier: `arcade-context-withdrawal-unknown` is the same unknown
+ * on the revoke, and it is answered the same way.
+ *
+ * WHAT AN OPERATOR CAN NOW SEE, AND DO WITH IT. One line, `arcade-mint-outcome-unknown`,
+ * naming the server, the action and `attemptStartedAt`. The attempt row is
+ * `pending_authorizations` at that `(server_id, action, created_at)`, holding NULL `auth_id`.
+ * The vendor-side flow, if one exists, is a pending authorization for this action on this
+ * account started at that instant. Nobody was given its url, so nobody can complete it. An
+ * operator who needs it gone ends it at the vendor; otherwise it lapses there and the claim
+ * lapses here. A run of these lines is the vendor not answering, not one person's problem.
+ *
+ * WHAT IS NOT IN THE LINE. No authorization id: none was read, and that is the point. No
+ * redirect url, the bearer capability; on the "answered with no id" path it existed and was
+ * dropped inside the adapter. No account or vendor user id, as on every sibling line. The
+ * reason goes through {@link reasonWithoutStatement}. Every error that reaches this branch
+ * carries this deployment's own sentence: `ArcadeFailureError` drops the vendor's prose by
+ * construction, the adapter's own refusals are ours, and {@link VendorTimeoutError} is ours.
+ *
+ * NOT AN AUDIT ROW. Nothing here writes to `pending_authorizations`, and the attempt's
+ * missing trail rows are already debt in `pending-authorization-audit-ledger.test.ts`, under
+ * the claim and the expiry overwrite (B5e). Filing an event for this one branch while the
+ * claim it follows files none would put half a lifecycle on the trail.
+ */
+ console.error(
+ JSON.stringify({
+ type: "arcade-mint-outcome-unknown",
+ server: serverId,
+ action,
+ attemptStartedAt: claim.attempt.createdAt,
+ note: "The vendor did not answer an authorization clearly, so whether it started a flow is unknown. The attempt was left standing with no authorization id: it cannot complete, it holds this person until it is fifteen minutes old, and nobody was sent to a consent screen. If the vendor shows a pending authorization for this action begun at attemptStartedAt, nobody here can finish it; end it there if it must not linger.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ /*
+ * L4a-b: THE SENTENCE NAMED A STEP THIS BRANCH MAKES IMPOSSIBLE.
+ *
+ * It used to end "finish it if you were sent to a consent screen" — and the line logged
+ * immediately above says, of the very same branch, that NOBODY WAS SENT TO ONE. Both cannot
+ * be true, and the log is the one that is: this branch is reached when the MINT did not
+ * answer clearly, so there is no authorization url to hand back and the browser never left
+ * this page. An instruction nobody can follow is worse than no instruction — somebody goes
+ * looking through their tabs and their mail for a consent screen that was never opened, and
+ * concludes they lost it.
+ *
+ * WHAT REPLACES IT IS WHAT THEY CAN ACTUALLY DO, in the register the two sibling refusals on
+ * this route already use for the same predicament (the uncertain-mint and unrecorded-mint
+ * sentences below): nothing opened, nothing connected, whether a flow started at the vendor
+ * is unknown, one that did cannot be finished from here, and the wait is bounded and named.
+ *
+ * THE WAIT IS NOT A SUGGESTION AND IS NOT DROPPED. The attempt is deliberately left standing
+ * precisely because a flow MAY be live, so "cancel it and start again" would be the wrong
+ * advice however much friendlier it reads — it is the hold that keeps a second authorization
+ * from being minted beside a first nobody can see.
+ */
+ return context.json(
+ {
+ error:
+ "The app's vendor did not answer clearly, so nobody was sent to a consent screen and nothing was connected. Whether an authorization started at the vendor is unknown, and one that did cannot be finished from here. This attempt expires within fifteen minutes, and starting over works after that.",
+ },
+ 502,
+ );
+ }
+ // A null id is recorded as NULL: an attempt holding none can never complete and simply expires.
+ let bound: boolean;
+ try {
+ bound = await store.recordAuthorizationMint({
+ actorId: context.var.actor.id,
+ createdAt: claim.attempt.createdAt,
+ authId: minted.authorizationId,
+ });
+ } catch (error) {
+ /*
+ * THE ONE WRITE ON THIS ROUTE THAT RUNS AFTER SOMETHING IRREVERSIBLE, AND IT USED TO BE THE
+ * ONLY CALL HERE WITH NO GUARD AT ALL.
+ *
+ * The mint SUCCEEDED: a flow is live at the vendor. Unhandled, a transient fault on this one
+ * local UPDATE — a statement timeout, a pool exhausted, a failover — threw past the handler
+ * into a BODYLESS 500, and left the claim standing with a NULL `auth_id`. That row is the
+ * worst of both halves. It can never complete, because the return leg matches the attempt on
+ * `auth_id` and a null matches no `flow_id` the browser can bring back; and it still HOLDS
+ * THE ACTOR, because `claimAuthorizationAttempt` refuses a second start until the row is
+ * fifteen minutes old. So one local hiccup turned a successful authorization into a person
+ * locked out of retrying, told nothing, with a live flow at Arcade that this deployment has
+ * no record of.
+ *
+ * THE ATTEMPT IS DISCARDED RATHER THAN LEFT TO EXPIRE, and this is NOT the uncertain-mint
+ * branch above turned around. There, the row is left standing because it may yet be the
+ * record of a live flow and deleting it would free the actor to run a second flow beside one
+ * that could still complete. Here the row provably records NOTHING: the id that would let it
+ * complete is exactly the value that failed to be written. Keeping it buys no safety and
+ * costs the person a quarter of an hour of refusals. Matched on `(actor_id, created_at)`, so
+ * a slow arrival here can never delete a newer attempt, and wrapped in its own `try` because
+ * the fault that broke the write above is likely to break this one too — a failed cleanup
+ * must not replace this refusal with the bodyless 500 it exists to prevent.
+ *
+ * AND THE MINT IS NOT LOST. The console line is what is left to carry it once the database
+ * has refused the row, and it holds the `ar_...` the vendor answered — the plain-text value
+ * this route was about to store — so an operator can find that flow at the vendor and end it.
+ * THE REDIRECT URL IS NOT LOGGED AND NEVER WILL BE: that is the bearer capability, and
+ * whoever opens it attaches the account. The error goes through
+ * {@link reasonWithoutStatement} for the reason `oauth-connection-not-recorded` gives: a
+ * `DrizzleQueryError`'s own message is the statement and every value bound to it.
+ */
+ let freed = false;
+ try {
+ freed = await store.discardAuthorizationAttempt({
+ actorId: context.var.actor.id,
+ createdAt: claim.attempt.createdAt,
+ });
+ } catch {
+ freed = false;
+ }
+ console.error(
+ JSON.stringify({
+ type: "arcade-mint-not-recorded",
+ server: serverId,
+ action,
+ authorizationId: minted.authorizationId,
+ attemptCleared: freed,
+ note: "An authorization was minted at the vendor and could not be written to its attempt. The person was told, was not sent to the consent screen, and the flow named here is live at the vendor with nothing here recording it.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ return context.json(
+ {
+ error: freed
+ ? "The app's vendor started that authorization, and this deployment could not record it — so it cannot be finished here and nothing was connected. Press Authorize again: the attempt was cleared, so starting over is safe."
+ : "The app's vendor started that authorization, and this deployment could not record it — so it cannot be finished here and nothing was connected. The attempt could not be cleared either; it expires within fifteen minutes, and starting over works after that.",
+ },
+ 500,
+ );
+ }
+ if (!bound) {
+ /*
+ * THE MINT WROTE NOTHING, SO THE CONSENT URL IS NOT HANDED OUT.
+ *
+ * `recordAuthorizationMint` answers false when no unminted, unsettled attempt stands under
+ * `(actor_id, created_at)`. On this route that is a cancel that landed while the vendor was
+ * minting: `DELETE /servers/:id/authorize` discarded the attempt (and filed
+ * `mcp.authorization_cancelled` for it), and only then did the vendor answer. The url it
+ * answered is a bearer capability for a flow with NO ATTEMPT BEHIND IT. Handing it out sent
+ * the person to grant access at the vendor against a record that does not exist, and the
+ * return leg then refused that consent as `no-attempt`, after the grant had been made. The
+ * person asked for nothing to be started, so nothing is.
+ *
+ * 409, THE STATUS THE CANCEL ROUTE ANSWERS WHEN IT AND A COMPLETION CROSS, because this is
+ * that kind of race seen from the other request: two acts on one attempt, and the one that
+ * lost is told so in an error a client cannot mistake for a url to follow. Nothing is
+ * discarded here: false means no attempt of this request's is left to discard, and a newer
+ * or settled row by the same actor is not this request's to touch.
+ *
+ * NOTHING IS LOGGED. The flow at the vendor is unreachable: its url was given to nobody and
+ * is written nowhere, so it expires there. The person's cancel is already on the trail.
+ * Neither the url nor the `ar_...` reaches this body.
+ */
+ return context.json(
+ {
+ error:
+ "That authorization was cancelled before the app's vendor finished starting it, so you were not sent on and nothing was connected. Press Authorize to start again.",
+ },
+ 409,
+ );
+ }
+ return context.json({ authorizationUrl: minted.redirectUrl });
+ });
+
+ /**
+ * Cancel THIS actor's authorization attempt, so somebody who closed the consent tab need not wait
+ * out the fifteen-minute TTL. Answers `{ cancelled }`: whether an attempt was removed.
+ *
+ * CANCELLING WHEN NO ATTEMPT EXISTS IS NOT AN ERROR — the attempt may have expired, failed or
+ * been cancelled already, and the person's intent (nothing in flight, nothing connected) is met.
+ *
+ * A CONSENT THAT COMPLETED UNDER THE CANCEL IS THE ONE CASE WHERE THAT INTENT IS NOT MET, AND IT
+ * IS ANSWERED 409 RATHER THAN `{ cancelled: true }` OR `{ cancelled: false }`. The attempt is read
+ * and then discarded, and a completion can commit between the two. `discardAuthorizationAttempt`
+ * refuses a settled row, so the record of that consent survives; but the account IS connected
+ * now, and neither boolean says so — `true` would be a lie and `false` reads as "there was nothing
+ * to cancel". The person asked for nothing to be connected and something was, so they are told
+ * which, in an error a client cannot mistake for success, and pointed at the one thing that undoes
+ * it: disconnecting. Any other settled outcome (failed, unreconciled, withdrawn) connected nothing,
+ * so it is the ordinary `{ cancelled: false }`.
+ *
+ * MATCHED ON `(actor_id, created_at)`, NEVER ON `auth_id`: an attempt whose mint never completed
+ * has no `auth_id` and is exactly the one most worth cancelling, and `created_at` is what stops
+ * this from deleting a NEWER attempt by the same actor that replaced the one resolved here. It
+ * goes through `discardAuthorizationAttempt`, the same deletion the mint's definite-failure
+ * discard uses.
+ *
+ * `actorId` IS THE SESSION SUBJECT (`users.id`), NEVER A CREDENTIAL ACTOR. BEHIND `requireUser`
+ * AND NOT ADMIN-GATED AT THE ROUTE, as the disconnect route above: `brokeredAccountFor` is where
+ * a Shared app is refused to anybody but an administrator.
+ *
+ * THE ATTEMPT IS RESOLVED BEFORE THE APP IS, AND THAT ORDER IS THE WHOLE POINT. Cancelling is an
+ * operation on the ATTEMPT ROW, and this route reads NOTHING off `brokeredAccountFor`'s success
+ * branch — not the toolkit, not the account, not the title. The deletion is keyed
+ * `(actor_id, created_at)` off the row itself. So resolving the app first bought no fact this
+ * route uses and cost the one case that needs cancelling most: AN ATTEMPT OUTLIVES ITS APP. Once
+ * an administrator removes the row, `brokeredAccountFor` answers `appOfUrl(null)` → "That app is
+ * not reached through a broker", 400, and a person holding a live pending authorization on an app
+ * that is gone could never retract it — while `claimAuthorizationAttempt` went on refusing them a
+ * new one for the full fifteen minutes. A not-found about the app, in answer to a request about
+ * their own attempt.
+ *
+ * DROPPING THE GATE FOR THAT CASE GIVES NOBODY ANYTHING THEY DID NOT ALREADY HAVE. The row
+ * reached here is the signed-in person's OWN attempt, by the only key the table has, and the act
+ * is its deletion; the administrator requirement on a Shared app guards that app's deployment
+ * account, which this route never touches. The one actor it newly admits is somebody whose
+ * authority changed while their own consent was in flight — and letting them retract what they
+ * started is the right answer, not a hole.
+ *
+ * THE REFUSALS SURVIVE WHERE THEY STILL MEAN SOMETHING, which is the branch with nothing of this
+ * actor's to cancel. A DELETE aimed at an ordinary OAuth row, or at no row at all, is then still
+ * told so in the words every sibling route uses, rather than being flattened into a cheerful
+ * `{ cancelled: false }` that claims the request was understood.
+ *
+ * AND IT CANCELS THE ATTEMPT THE PATH NAMES, NEVER MERELY "WHATEVER THIS ACTOR IS HOLDING". The
+ * attempt key is the actor alone, so the lookup can only answer that one row — but the request
+ * names an app, and an attempt standing on a DIFFERENT app is not the thing being cancelled.
+ * Deleting it anyway killed an in-flight consent begun from another screen and answered
+ * `{ cancelled: true }` as though it had done what was asked, which is the same broken
+ * correlation the mint/claim/complete path exists to hold from the other end. A standing attempt
+ * on another app is therefore the `{ cancelled: false }` of "no attempt for THIS app", which is
+ * the same answer, and the same non-error, as there being no attempt at all.
+ *
+ * THE SERVER IS COMPARED AGAINST THE RAW PATH PARAMETER, which is exactly what
+ * `POST /servers/:id/authorize` writes into `server_id` when it claims the row. Comparing against
+ * anything derived — the toolkit, the resolved title — would be two spellings of one fact and a
+ * place for them to drift apart.
+ */
+ routes.delete("/servers/:id/authorize", requireUser, async (context) => {
+ const serverId = context.req.param("id");
+ const actorId = context.var.actor.id;
+ const attempt = await store.authorizationAttemptFor(actorId);
+ if (attempt && attempt.serverId === serverId) {
+ const cancelled = await store.discardAuthorizationAttempt({
+ actorId,
+ createdAt: attempt.createdAt,
+ });
+ /*
+ * A RETRACTION IS ON THE TRAIL, AND ONLY ONE THAT HAPPENED. The person withdrawing their own
+ * pending consent is the other deliberate ending of this lifecycle besides completing it, and
+ * it is filed for the same reason `mcp.account_connected` is. Only when the deletion removed
+ * a row: a cancel that lost a race to a newer attempt removed nothing and withdrew nothing.
+ * After the deletion and not around it, as the disconnect route files its row, so a failed
+ * audit write fails the request rather than passing silently.
+ */
+ if (cancelled) {
+ await store.recordAuthorizationCancelled({ actorId, attempt });
+ } else if (
+ /*
+ * AND A CANCEL THAT LOST TO THE COMPLETION SAYS SO. The discard refuses a settled attempt,
+ * so `cancelled` is false here for two different reasons; only one of them is "nothing was
+ * standing". Answering `{ cancelled: false }` to a person whose consent completed would
+ * leave them believing they had retracted an account that is now connected.
+ */
+ (await store.settledAuthorizationOutcome({
+ actorId,
+ createdAt: attempt.createdAt,
+ })) === "completed"
+ ) {
+ return context.json(
+ {
+ error:
+ "That authorization finished before it could be cancelled, so the account is connected. Disconnect it if you did not mean to connect it.",
+ },
+ 409,
+ );
+ }
+ return context.json({ cancelled });
+ }
+ const resolved = await brokeredAccountFor(context, serverId);
+ if (resolved.refusal) {
+ return context.json(
+ { error: resolved.refusal.error },
+ resolved.refusal.status,
+ );
+ }
+ return context.json({ cancelled: false });
+ });
+
+ /**
+ * Where a person's BROWSER lands after a brokered per-action consent: the verifier.
+ *
+ * THIS ROUTE BREAKS THIS FILE'S JSON/401 CONVENTION DELIBERATELY, AND THE REASON IS WRITTEN HERE
+ * SO NOBODY "FIXES" IT TO MATCH ITS NEIGHBOURS. Every other route in this file answers `fetch`
+ * from OpenBot's own pages, so JSON is what the caller can read and 401 is what an unauthenticated
+ * one deserves. Here the caller is a PERSON'S BROWSER, arriving on a top-level navigation from the
+ * broker's own screen with a just-completed consent behind it — so an unauthenticated arrival is
+ * sent to sign-in with a 303 and a `Location` CARRYING ITS `flow_id`, never answered 401. A 401
+ * here is somebody who has just granted a third party access to their mailbox being shown a raw
+ * error object on an API origin that serves no pages.
+ *
+ * WHICH IS WHY `requireUser` IS RUN BY HAND RATHER THAN MOUNTED IN FRONT OF THE HANDLER. The guard
+ * answers 401 JSON, and the whole point of this route is that it must not. It is still the same
+ * guard — the session is read, the role is resolved and `actor` is set by it, so nothing here
+ * re-implements authentication — and only its REFUSAL is translated into the redirect. The
+ * composition is the one `admin/gate.ts` already uses: call it with a `next` that records that it
+ * ran.
+ *
+ * SIGNING BACK IN AS THE SAME PERSON THEN COMPLETES NORMALLY, because the attempt is keyed on the
+ * signed-in person. Signing in as somebody else finds no attempt and refuses, which is correct and
+ * occasionally annoying.
+ *
+ * CRITERION: NO REQUEST TO THIS ENDPOINT ENDS WITHOUT A `Location`, WITH ONE STATED EXCEPTION.
+ * That is the criterion `/oauth/callback` below already holds itself to in as many words, and this
+ * is the file's second browser-navigation return leg — so a refusal here ends the same way that
+ * one does: back at the connected-accounts LIST, with a word about what happened and nothing
+ * written. The reason travels as an opaque code on `?authorized=`
+ * ({@link AUTHORIZATION_REFUSALS}); the sentence belongs to the app, which is the only place it is
+ * written down. THE LIST AND NOT AN APP PAGE, uniformly: three of the refusals have no attempt row
+ * to build an app address from, and {@link connectedAccountsUrlFor} already argues that picking
+ * one would be a guess about what somebody had been doing.
+ *
+ * THE EXCEPTION IS THE 403, AND IT IS STATED RATHER THAN INHERITED. The guard that refuses a
+ * session with no role is the same guard `/api/me` answers from, so such a session cannot reliably
+ * load the landing page either — it is sent to `/sign` or into an error boundary, which is the
+ * dead end that branch exists to avoid. It keeps its JSON body.
+ */
+ routes.get("/arcade/verify", async (context) => {
+ /**
+ * Where a refusal lands, which is the LIST screen with the reason beside it.
+ *
+ * Built from {@link connectedAccountsUrlFor}'s failure address with its query dropped, so the
+ * one place that knows how to address the app's origin stays the one place that knows — and so
+ * that `?connected=failed`, which belongs to the Composio callback, is NOT carried along beside
+ * `?authorized=` and drawn as a second notice about something that did not happen.
+ */
+ const refusedWith = (reason: AuthorizationRefusal) => {
+ const [screen] = connectedAccountsUrlFor(connect?.appUrl, {
+ failed: true,
+ }).split("?");
+ return context.redirect(`${screen}?authorized=${reason}`, 303);
+ };
+
+ /*
+ * Read, not yet judged. The redirect below has to carry it, and that redirect happens before
+ * the value is validated — reading a query string calls nobody and writes nothing.
+ */
+ const flowId = (context.req.query("flow_id") ?? "").trim();
+
+ /**
+ * WHETHER THE COMPLETION TRANSACTION IS KNOWN TO HAVE COMMITTED, and the one thing the two
+ * `catch`es below have to agree about. Declared out here, ahead of the `try`, because the
+ * blanket `catch` has to be able to tell a throw over a live, recorded grant from a throw over
+ * nothing at all — which is the distinction it used to answer `not-recorded` for both halves
+ * of. Set in exactly one place: the completion's own `catch` below.
+ */
+ let recorded = false;
+
+ /*
+ * EVERYTHING FROM HERE IS IN ONE `try`, FOR THE REASON `/oauth/callback` BELOW GIVES FOR ITS
+ * OWN: so it is the whole body rather than each call. A blanket `catch` is a promise about the
+ * route; a per-call one is a promise about the calls somebody remembered. The criterion is
+ * that no request to this endpoint ends without a `Location`, and a call that throws would
+ * otherwise put somebody who has just consented at another company on a bodyless 500 — there
+ * is no `app.onError` on this app to turn that into anything else, and this is an origin that
+ * serves no pages.
+ *
+ * IT OPENS HERE AND NOT AT THE COMPLETION, which is where it used to open and where it was a
+ * promise about the calls somebody remembered. Two awaited reads happen first and both reach a
+ * store: `requireUser` reads the session and then this actor's roles, and
+ * `authorizationAttemptFor` is the one query on the happy path. A database blip or an
+ * exhausted pool during the attempt read is the ordinary way to reach either.
+ *
+ * THE STATED EXCEPTION IS UNTOUCHED BY THIS. The 403 for a session with no role RETURNS, and a
+ * `catch` sees throws and not returns, so it keeps its JSON body.
+ *
+ * The inner `try`s below take the vendor's throws first, so no refusal code changes here. AND
+ * THE COMPLETION NOW HAS ONE OF ITS OWN, which is not a hedge against this `catch` but the
+ * thing this `catch` cannot do: it is the only step on this leg that can leave a grant RECORDED
+ * on the way out, so it is the only one that can say whether `not-recorded` is true. See it.
+ */
+ try {
+ let signedIn = false;
+ const refusal = await requireUser(context, async () => {
+ signedIn = true;
+ });
+ if (!signedIn) {
+ /*
+ * ONLY A MISSING SESSION GOES TO SIGN-IN. The guard also refuses 403 for a session with no
+ * role, and sending that person to sign-in is a dead end: the sign-in screen's own load hits
+ * the same guard, gets the same 403, and they sign in as themselves again with nothing
+ * changed and never told their access was removed.
+ */
+ if (refusal instanceof Response && refusal.status === 401) {
+ return context.redirect(signInUrlFor(connect?.appUrl, flowId), 303);
+ }
+ return context.json(
+ {
+ error:
+ "This deployment no longer recognises you, so nothing was recorded. Ask an administrator to restore your access, then start the authorization again from the app's page.",
+ },
+ 403,
+ );
+ }
+
+ /*
+ * THE CAPABILITY GATE, AND IT IS A PRESENCE TEST ON A VALUE THE BROKER DECLARED. Never a
+ * `provider === "arcade"` comparison: see {@link ConnectedAppBroker.authorizations}, and
+ * `store.ts`'s gate comment, which records a defect where a gate and its exemption keyed on
+ * different things and the exemption failed OPEN. A deployment whose broker runs no correlated
+ * authorization flow has no return leg for this route to verify.
+ */
+ if (!composio) return refusedWith("no-broker");
+ const flow = composio.broker.authorizations;
+ if (!flow) return refusedWith("not-supported");
+
+ /*
+ * A HANDLE THIS DEPLOYMENT CANNOT USE IS REFUSED WITHOUT ASKING THE VENDOR ANYTHING. Empty,
+ * missing or whitespace-only are one fact — the browser arrived without the one thing the
+ * return leg carries — and spending a vendor call on it would be an unauthenticated-shaped
+ * request reaching another company's API.
+ */
+ if (!flowId) return refusedWith("missing-handle");
+
+ /*
+ * THE ATTEMPT COMES FROM THE SIGNED-IN ACTOR AND NEVER FROM THE HANDLE. The vendor's return-leg
+ * id and the id its mint answered with are different id spaces with no endpoint between them,
+ * and nothing here may try to bridge them; trying identities until one is accepted would be an
+ * oracle reporting which of this deployment's identities have flows outstanding. The session
+ * names exactly one candidate, which is what makes the correlation exact rather than probable.
+ *
+ * {@link PluginStore.authorizationAttemptFor} already excludes a completed attempt and one past
+ * the fifteen-minute TTL, so all three absences arrive here as `null` and are refused in one
+ * sentence. A NULL `auth_id` is the fourth: its mint never completed, so there is nothing for
+ * the binding below to be checked against and the attempt can only ever expire.
+ *
+ * A ROW THAT CANNOT SAY WHOSE ACCOUNT IT IS FOR IS THE FIFTH, AND IT IS REFUSED HERE RATHER
+ * THAN READ AS THE DEPLOYMENT'S. `(holder, user_id)` is held by
+ * `pending_authorizations_holder_check`, so a `person` row with no user id cannot be written
+ * and this branch is unreachable through the table — which is exactly why it must be a
+ * refusal: the reading it replaces rebuilt that row as `{ holder: "deployment" }` and then
+ * aimed the withdrawal below at the DEPLOYMENT's account, ending a connection nobody asked
+ * about while the person's stayed live. Refused BEFORE the vendor is asked anything, like the
+ * null `auth_id` above and for the same reason: the attempt can never complete, so no call is
+ * worth spending on it, and nothing is written — the row stands until its TTL.
+ */
+ const actorId = context.var.actor.id;
+ const attempt = await store.authorizationAttemptFor(actorId);
+ if (!attempt || attempt.authId === null) return refusedWith("no-attempt");
+ const attemptAccount = asAccountRef(attempt);
+ if (!attemptAccount) return refusedWith("no-attempt");
+ const authId = attempt.authId;
+
+ /*
+ * THE BINDING. `handle` IS AN OPAQUE STRING THE SEAM DOES NOT INTERPRET — the vendor's word for
+ * it stops at the adapter, and nothing here parses it, compares it to a scheme or reads a
+ * provider out of it.
+ *
+ * THE MISMATCH BRANCH IS ASSUMPTION-BACKED AND IS MOCKED-ONLY UNTIL A LIVE PROBE RUNS. The SDK
+ * declares no error shape for a handle belonging to somebody else; the adapter detects one by
+ * normalizing the thrown error and reading `user_mismatch`, and RETHROWS anything else. If the
+ * vendor's real shape differs, `confirmReturn` does not quietly answer `{ mismatch: true }` —
+ * it throws, and the branch below surfaces an outage instead of a clean refusal. That is UX
+ * degradation and not a hole: nothing is written and the attempt row is left alone either way,
+ * and the `auth_id` comparison underneath independently covers a `confirm` that SUCCEEDS
+ * against the wrong identity. Do not read a passing mocked test as proof this path works.
+ */
+ let confirmed: Awaited>;
+ try {
+ confirmed = await withVendorDeadline(
+ (signal) =>
+ flow.confirmReturn(
+ {
+ handle: flowId,
+ vendorUserId: attempt.vendorUserId,
+ },
+ { signal },
+ ),
+ STATUS_CALL_TIMEOUT_MS,
+ );
+ } catch {
+ /*
+ * NOTHING FROM THE VENDOR'S ANSWER TRAVELS WITH THIS — not its sentence, not its body, not
+ * the error. A reply on this leg can carry an authorization url, which is a bearer capability,
+ * and an outage is not worth a person's consent being quotable out of an error body.
+ *
+ * LOGGED, THOUGH, WHICH IT WAS NOT. This refusal and the status one below are the two ways a
+ * return leg ends with the vendor unreachable, and they reach the person as the SAME
+ * sentence — so without a line an operator cannot tell which call failed, and the two are
+ * not equally serious: this one ran BEFORE anything was confirmed, so no completion is
+ * implied by reaching it. Said plainly here rather than left to be inferred from the absence
+ * of the other line.
+ *
+ * AND NO REASON TRAVELS WITH IT, for the reason the paragraph above already gives about the
+ * redirect: a reply on this leg can carry an authorization url, which is a bearer
+ * capability. That hazard does not stop at the response — it was measured reaching this
+ * line — and a log is not a lesser destination. Both vendor calls on this route therefore
+ * log what happened and where to look, and nothing derived from what the vendor said. See
+ * `arcade-completion-status-unknown` below, which drops its reason for the same reason and
+ * over a worse secret.
+ */
+ console.error(
+ JSON.stringify({
+ type: "arcade-return-confirmation-unknown",
+ server: attempt.serverId,
+ action: attempt.action,
+ authorizationId: authId,
+ attemptStartedAt: attempt.createdAt,
+ note: "A person returned from a consent screen and the vendor could not be asked whether this return belongs to their authorization, so nothing was read and nothing was written. The attempt was left standing and returning to the link can still settle it. Unlike arcade-completion-status-unknown this implies no completed consent: the status was never reached. No reason is carried here on purpose: a reply on this leg can hold an authorization url, which is a bearer capability.",
+ }),
+ );
+ return refusedWith("vendor-unavailable");
+ }
+
+ /*
+ * TWO REFUSALS, ONE SENTENCE, AND THE ATTEMPT ROW IS LEFT UNTOUCHED BY BOTH. A handle the vendor
+ * says belongs to somebody else, and a confirmation naming a DIFFERENT authorization than the
+ * one this deployment minted, are both "this return leg is not for the attempt you are holding".
+ *
+ * THE `auth_id` COMPARISON IS NOT OPTIONAL AND IT IS NOT HOPEFUL: the confirmation's id was
+ * measured IDENTICAL to the mint's through a completed consent, so this is an exact equality
+ * over one id space. Identity alone is not correlation — an expired read attempt, a fresh send
+ * attempt under the same identity, and then the read's callback arriving late matches on
+ * identity at every step, and a check stopping there would attribute a read consent to a send
+ * request. Local expiry does not cancel a vendor flow, so late callbacks are ordinary.
+ *
+ * NOTHING IS WRITTEN AND NOTHING IS CLOSED. A late callback must never consume or ALTER the
+ * replacement it arrived behind, so the row is not deleted, not completed and not marked.
+ */
+ if ("mismatch" in confirmed || confirmed.authorizationId !== authId) {
+ return refusedWith("not-your-authorization");
+ }
+
+ /*
+ * THE BOUNDED WAIT. `pending` is polled a few times over a few seconds and never with the SDK's
+ * unbounded `waitForCompletion`: a browser is held open on this request, and a vendor that has
+ * stopped answering must not hold it open forever. See {@link settledStatus}.
+ *
+ * NOTHING FROM THAT ANSWER TRAVELS ANYWHERE (D31). The vendor's own status reply carries a LIVE
+ * PROVIDER ACCESS TOKEN and the person's full profile; the seam narrows it to three fields
+ * inside the adapter, and the three are read here into a decision, a comparison and the scope
+ * list the completion writes. Not one of them is logged, attached as a `cause` or put in an
+ * error body, and the status object itself reaches none of those either. A status response
+ * LOOKS LIKE METADATA, which is how the throwaway harness that proved this flow came to write
+ * the token to a file on its first run.
+ */
+ let settled: Awaited>;
+ try {
+ settled = await settledStatus(flow, authId);
+ } catch {
+ /*
+ * THE ONE UNKNOWN ON THIS LEG THAT CAN SIT OVER A LIVE GRANT, AND IT USED TO BE SILENT.
+ *
+ * Everything above has already passed: the handle was confirmed, it named THIS mint, and the
+ * person is standing here because a consent screen sent them. So the likeliest state behind
+ * this throw is a consent that COMPLETED AT THE VENDOR — and the only thing that failed is
+ * this deployment reading the status that would let it record one. Nothing is written, so the
+ * grant is live at Arcade and no row here names it.
+ *
+ * THE ROW IS LEFT STANDING, DELIBERATELY, AND THAT IS WHY A LINE IS THE WHOLE FIX. A status
+ * this deployment could not read is not a verdict: closing the attempt would take away the
+ * link the person can come back to, and writing a connection would record a completion
+ * nothing here has evidence of. The attempt stands until its TTL and a return can still
+ * settle it — so the recovery already exists, and what was missing was anybody knowing to
+ * look. See `arcade-mint-outcome-unknown`, which is this shape on the opening leg.
+ *
+ * AND THIS LINE CARRIES NO REASON, WHICH IS THE ONE PLACE IT DEPARTS FROM ITS SIBLINGS.
+ *
+ * `arcade-mint-outcome-unknown` and `arcade-context-withdrawal-unknown` both log the error
+ * through {@link reasonWithoutStatement}, on the argument that everything reaching them
+ * carries this deployment's own sentence. THAT ARGUMENT DOES NOT HOLD HERE and it was
+ * MEASURED not holding: this is the status leg, whose reply carries a LIVE PROVIDER ACCESS
+ * TOKEN and the person's profile (D31) — the adapter already inverts the carry-the-vendor-
+ * error-as-`cause` convention for exactly that reason — and a failure injected on this seam
+ * put the token straight into the line. A log is not a lesser destination than a redirect,
+ * and this leg's whole discipline is that nothing of that answer travels anywhere.
+ *
+ * So the line names WHAT HAPPENED AND WHERE TO LOOK, and nothing derived from the vendor:
+ * the authorization id an operator finds the flow by, the app, the action, and when the
+ * attempt began. The reason is the one thing that cannot be included without reopening the
+ * hole this leg exists to keep shut, and an operator holding the id can ask the vendor
+ * directly — which is the thing the missing reason would only have described.
+ *
+ * Held by `arcade-verify.test.ts`, which asserts the token reaches neither the response nor
+ * this line.
+ */
+ console.error(
+ JSON.stringify({
+ type: "arcade-completion-status-unknown",
+ server: attempt.serverId,
+ action: attempt.action,
+ authorizationId: authId,
+ attemptStartedAt: attempt.createdAt,
+ note: "A person returned from a consent screen and this deployment could not read whether the authorization completed, so nothing was recorded. The consent may well be LIVE at the vendor with no row here naming it. The attempt was left standing rather than closed, so returning to the link can still settle it; if it expires first, check the authorization at the vendor before assuming nothing was granted. No reason is carried here on purpose: the vendor's status answer holds a live access token.",
+ }),
+ );
+ return refusedWith("vendor-unavailable");
+ }
+
+ /*
+ * WHAT EACH OUTCOME DOES TO THE ATTEMPT ROW, AND THE TIMEOUT IS THE ONE THAT LEAVES IT ALONE.
+ * `failed` and an identity that is not the attempt's are verdicts, so the row is consumed and
+ * marked. STILL `pending` AT THE BOUND IS NEITHER A SUCCESS NOR A FAILURE: the consent may be
+ * finished a second later, and a row closed here would mean a person who came back to the
+ * link found nothing to finish. It stands until its TTL.
+ */
+ if (settled.status === "failed") {
+ await store.failAuthorizationAttempt({ actorId, authId });
+ return refusedWith("authorization-failed");
+ }
+ if (settled.status !== "completed") return refusedWith("not-finished");
+ if (settled.userId !== attempt.vendorUserId) {
+ await store.failAuthorizationAttempt({ actorId, authId });
+ return refusedWith("not-your-authorization");
+ }
+
+ /*
+ * FRESHNESS (D28): A CONSENT WHOSE CONTEXT HAS MOVED ON IS NOT COMPLETED INTO THE PRESENT.
+ *
+ * The app's CURRENT account, its mode, the active provider and this actor's current authority
+ * are all re-resolved through the same resolver the mint used, and all of them have to still
+ * be the attempt's. The window is one consent's TTL and it needs an administrator to switch an
+ * app's mode twice inside it — which is rare, and silent: writing it would create a live grant
+ * bound to a retired identity that nothing will ever call with, and on an app somebody
+ * disconnected in between it would quietly resurrect the account.
+ *
+ * AND THE GRANT MAY ALREADY BE LIVE AT THE VENDOR, because the person completed it there
+ * before this deployment looked. So the RETIRED identity's connection is withdrawn — keyed on
+ * that identity and on this app, which CANNOT TOUCH THE CURRENT ACCOUNT: a connection is keyed
+ * `(user_id, provider_id)` and the retired identity is not the current one. Where that
+ * withdrawal succeeds the attempt is marked `withdrawn`; where it fails, is not attempted or
+ * answers `false` it is kept as `unreconciled` rather than closed, because a grant
+ * we know about and could not end is worth a row somebody can act on.
+ */
+ /*
+ * THE RETIRED IDENTITY IS THE ATTEMPT'S OWN, READ ONCE AT THE TOP AND NOT REBUILT HERE. A
+ * reference built a second time from the row's columns is a second chance to build it wrong,
+ * and the one this replaced defaulted a pair it did not recognise to the deployment's.
+ */
+ const retired: AccountRef = attemptAccount;
+ const fresh = await brokeredAccountFor(context, attempt.serverId);
+ if (fresh.refusal || !sameAccount(fresh.account, retired)) {
+ const row = await store.serverAddress(attempt.serverId);
+ const retiredApp = row ? appOfUrl(row.url) : null;
+ let withdrawn = false;
+ /*
+ * WHAT THIS WITHDRAWAL ENDS BESIDES THE APP IT NAMES, RESOLVED BEFORE THE REVOKE GOES OUT.
+ *
+ * Where a connection is keyed `(identity, auth provider)` — Arcade — the one revoke below
+ * ends the whole connection and so every app on the retired identity that shares it. Their
+ * rows here would be left standing, which makes this deployment's own records claim live
+ * connections for apps the vendor has already disconnected: a person sees apps listed as
+ * connected that fail on first use, and nothing here knows to tell them otherwise. The set
+ * is the store's, keyed on the pair and never on the provider alone, and BEFORE rather than
+ * after, which is the ordering `disconnectBrokered` and `removeServer` both keep: once the
+ * revoke has run, a sibling's auth provider is a question asked of a connection that is
+ * already gone. On Composio an app is its own auth provider, so it is always empty and
+ * nothing below differs from what this refusal always did.
+ *
+ * AND A SET THIS COULD NOT RESOLVE STOPS THE REVOKE RATHER THAN WIDENING IT. The ask is a
+ * live vendor call per app on a request a browser is held open on, so it is bounded — on
+ * the WHOLE resolution rather than on each call inside it, because the store's method takes
+ * no signal, which is the bound that matters to the browser. A timeout or a refusal leaves
+ * `collateral` null and nothing is then asked of the vendor at all. Ending apps this
+ * deployment cannot then name is the failure this whole branch is about, and the
+ * alternative is not silence: the attempt is kept `unreconciled` below, which is a row an
+ * operator can act on over a grant that is merely still live.
+ */
+ let collateral: { siblings: string[]; unanswered: number } | null =
+ null;
+ if (retiredApp) {
+ try {
+ collateral = await withVendorDeadline(() =>
+ store.siblingApps({ toolkit: retiredApp, account: retired }),
+ );
+ } catch (error) {
+ /*
+ * Nothing from the vendor's answer travels with this, here as everywhere on this leg.
+ *
+ * AND THE FIX THAT LOGGED THE REVOKE THROW DID NOT REACH ITS OWN SIBLING. `arcade-
+ * context-withdrawal-unknown` below covers the revoke raising; this call sits one step
+ * in front of it and decides whether that revoke is attempted AT ALL. Unresolved, the
+ * revoke is skipped entirely and the attempt is closed `unreconciled` — the same outcome
+ * the branch below writes a line for, reached by a different road and, until now,
+ * without one. An operator reading the trail saw `unreconciled` and nothing saying why.
+ */
+ collateral = null;
+ console.error(
+ JSON.stringify({
+ type: "arcade-sibling-resolution-unknown",
+ server: attempt.serverId,
+ app: retiredApp,
+ action: attempt.action,
+ authorizationId: authId,
+ note: "A consent completed at the vendor after this app's account changed, and which apps the retired identity's grant also covers could not be resolved. No withdrawal was attempted, so the retired identity's grant is still live at the vendor and this deployment's rows for it were left alone. The attempt was closed unreconciled. Disconnect the app here to settle it.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ }
+ }
+ if (retiredApp && collateral) {
+ try {
+ /*
+ * THE BOOLEAN IS READ, NOT DROPPED. `false` means "nothing there" — on Arcade a
+ * toolkit whose auth provider could NOT be resolved now refuses rather than answering
+ * it, so an unattributable app arrives here as the throw below. Both land on the
+ * not-withdrawn branch, which is the safe reading over a possibly-live grant.
+ */
+ withdrawn = await withVendorDeadline(() =>
+ composio.broker.revoke({
+ account: retired,
+ toolkit: retiredApp,
+ }),
+ );
+ } catch (error) {
+ /*
+ * A THROW HERE IS NOT AN ANSWER OF `false`, AND IT IS NOT READ AS ONE.
+ *
+ * `false` is the broker saying "there was nothing to withdraw", off a listing it read
+ * whole. A throw says nothing of the kind: the seam deletes EVERY connection it found
+ * and only then throws if any delete failed, so "revoke failed for 1 of 3" arrives
+ * here with two of three already gone; and a deadline that fires is this deployment
+ * giving up on a call the vendor may finish a moment later. Either way the vendor side
+ * may already be withdrawn — the commit-then-throw shape `/arcade/verify`'s completion
+ * catch below was fixed for, one call earlier on the same leg.
+ *
+ * SO THE OUTCOME IS NEITHER CLAIM. `withdrawn` stays false, which is the conservative
+ * half: no local row is cleared and no collateral is put on the trail on the strength
+ * of a withdrawal nobody saw, because the alternative — clearing on a guess — is
+ * this deployment forgetting a grant that may still be live, and that is the
+ * irreversible mistake. The attempt is closed `unreconciled`, which is the existing
+ * verdict for "we do not know what happened" and is exactly true here.
+ *
+ * WHAT `unreconciled` DOES NOT COVER, AND THIS LINE DOES. The row holds an outcome and
+ * a timestamp and nothing else: no reason, nothing reads it back, and it reads the same
+ * for a vendor that said "nothing there" as for a call whose outcome is unknown. It
+ * cannot say the one thing an operator needs here — that the vendor may ALREADY have
+ * ended this connection, so the rows this deployment still holds for the app and for
+ * every sibling on the same connection may be claiming live connections that are gone,
+ * or that the grant is live and needs ending by hand. That is a disagreement between
+ * this deployment and the vendor that nobody would otherwise find out about, and
+ * leaving no trace of it was half of the defect. A third outcome on the row would need
+ * a schema change to say what this line says, and the line is where every sibling on
+ * this route already reports a failure it swallowed.
+ *
+ * WHAT IS IN THE LINE is what `arcade-completion-not-recorded` allows itself and the
+ * names of this deployment's own apps: the authorization id an operator finds the flow
+ * by, the app, the sibling apps whose rows may now be stale, and the reason through
+ * {@link reasonWithoutStatement}. Errors out of the broker seam carry this deployment's
+ * own sentence — `ArcadeFailureError` (`arcade.ts`) drops the vendor's prose by construction
+ * and {@link VendorTimeoutError} is ours — so nothing of the vendor's answer travels
+ * with this, here as everywhere on this leg. No account id, no vendor user id.
+ */
+ withdrawn = false;
+ console.error(
+ JSON.stringify({
+ type: "arcade-context-withdrawal-unknown",
+ server: attempt.serverId,
+ app: retiredApp,
+ action: attempt.action,
+ authorizationId: authId,
+ siblings: collateral.siblings,
+ note: "A consent completed at the vendor after this app's account changed, and withdrawing the retired identity's grant raised instead of answering. Whether the vendor withdrew it is unknown: the attempt was closed unreconciled and no row here was cleared, so either the grant is still live at the vendor or this deployment's rows for the app and the siblings named here claim connections the vendor has already ended. Check the connection at the vendor and disconnect the app here to settle it.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ }
+ }
+ /*
+ * AND THE ROWS FOR WHAT IT ENDED GO WITH IT, ONLY ON AN ANSWER OF TRUE. `false` is "nothing
+ * found, or we could not work out what to withdraw", and no local row may be cleared — nor
+ * any collateral claimed on the trail — on the strength of an answer nobody got. The trail
+ * rows this writes are what tell a person why an app they never touched stopped being
+ * connected here, and tell an operator that one withdrawal took several apps with it.
+ */
+ if (withdrawn && retiredApp && collateral) {
+ await store.clearWithdrawnAccount({
+ toolkit: retiredApp,
+ account: retired,
+ siblings: collateral.siblings,
+ unanswered: collateral.unanswered,
+ // The actor this attempt is keyed on, read once at the top like everything else here.
+ by: actorId,
+ reason: "context_changed",
+ });
+ }
+ if (withdrawn)
+ await store.markAuthorizationWithdrawn({ actorId, authId });
+ else await store.markAuthorizationUnreconciled({ actorId, authId });
+ return refusedWith("context-changed");
+ }
+
+ /*
+ * ONE TRANSACTION, AND A REPLAY IS A NO-OP RATHER THAN AN ERROR. The attempt row is the
+ * serialization point: zero rows updated means somebody else completed this attempt first, and
+ * the loser is answered exactly as the winner is and writes nothing. See
+ * {@link PluginStore.completeAuthorizationAttempt}.
+ *
+ * `authProvider` IS ASKED OF THE BROKER AND NEVER DERIVED FROM THE TOOLKIT NAME. The two
+ * coincide only where an operator happened to name their provider configuration after the
+ * app, which {@link ConnectedAppBroker.authProviderOf} sets out at length — so a derivation
+ * would pass every test in this tree and be wrong about the deployment it shipped to. A
+ * vendor that does not say is recorded as not saying, and a vendor that cannot be reached for
+ * it costs nobody their completed consent: this is an audit label, and the grant is already
+ * live. It is never read back out of a status reply, which D31's narrowing makes
+ * structurally impossible anyway.
+ */
+ const authProvider = await withVendorDeadline(() =>
+ composio.broker.authProviderOf(fresh.toolkit),
+ ).catch(() => null);
+
+ /*
+ * AND THE LANDING COMES FROM THE ATTEMPT ROW (D18), NEVER FROM `next_uri`. The vendor refuses
+ * every `next_uri` at mint, and what `confirm_user` answers is the vendor's OWN success page
+ * rather than ours. The row holds the app and the action this consent was for, and it cannot
+ * be missing here — this handler refused long before it needed an address — so there is no
+ * fallback to specify.
+ *
+ * AND BOTH HOLDERS LAND ON THE SAME SCREEN, which is the only one that reads `?action=`. An
+ * account the deployment holds used to be sent to `/admin/plugins/:key`, but that route has no
+ * `validateSearch` for `action`, no landing notice and no grant list, so the whole Shared-app
+ * flow completed a consent at the vendor and landed somewhere that said nothing about it. The
+ * settings page draws the Shared app's grant list and the landing notice for either holder,
+ * so this is where the action somebody just authorized is actually named and marked.
+ *
+ * `connectedAccountsUrlFor` with `{ serverId }` answers a query-less path, so appending
+ * `?action=` here cannot produce a second `?`.
+ *
+ * BUILT BEFORE THE WRITE RATHER THAN AFTER IT, so that the only thing standing between a
+ * committed grant and the person's browser is the redirect itself. Both reads are off the
+ * attempt row and neither calls anybody.
+ */
+ const landing = `${connectedAccountsUrlFor(connect?.appUrl, {
+ serverId: attempt.serverId,
+ })}?action=${encodeURIComponent(attempt.action)}`;
+
+ /*
+ * THE COMPLETION HAS ITS OWN `catch`, AND IT IS THE ONLY PLACE ON THIS LEG THAT MAY ANSWER
+ * `not-recorded`.
+ *
+ * THE DEFECT IT CLOSES, FIRST HALF: the blanket `catch` below used to answer `not-recorded`
+ * for every throw in this handler INCLUDING ONE RAISED AFTER THIS TRANSACTION HAD COMMITTED.
+ * The grant is live at the vendor and the row is written here, and the person was told it was
+ * not recorded — so they start over, mint a second consent against a connection they already
+ * have, and the one sentence this leg exists to get right is the one it got backwards.
+ *
+ * THE TWO ARE NOT TOLD APART BY WHETHER THE CALL THREW. The transaction is all-or-nothing
+ * ({@link PluginStore.completeAuthorizationAttempt}), but "the call rejected" and "the work
+ * rolled back" are different facts: a connection lost on the way back from a COMMIT the
+ * server already applied rejects here over a grant that IS written down. So the row the
+ * transaction serializes on is ASKED
+ * ({@link PluginStore.authorizationAttemptCompleted}) rather than inferred from the throw,
+ * and that answer — not the throw — decides which sentence the person gets. A probe that
+ * cannot be reached either reads as not recorded, which is the reading that sends somebody
+ * back to try again rather than away believing in a grant that is not there; the completion
+ * is idempotent on `completed_at IS NULL`, so a retry over a grant that did land writes
+ * nothing twice.
+ *
+ * SECOND HALF: IT IS LOGGED, which the blanket `catch` did not do at all. This is the one leg
+ * where a vendor grant can be LIVE with nothing here recording it, and it was also the one
+ * leg that left an operator no trace of that happening — every sibling on this route logs,
+ * and this is the same line `arcade-mint-not-recorded` writes at the other end of the same
+ * lifecycle, for the same reason: the `ar_…` is what lets an operator find that flow at the
+ * vendor and end it.
+ *
+ * WHAT IS IN THE LINE IS EXACTLY WHAT IS SAFE TO PUT THERE. The authorization id, the app and
+ * the action: the first is the handle the mint log already carries, and the other two are
+ * this deployment's own catalogue names. NOT the `authorization.url`, which is the bearer
+ * capability whoever opens it attaches the account with and which this handler never holds a
+ * copy of anyway; NOT the status reply, which carries a LIVE PROVIDER ACCESS TOKEN and the
+ * person's profile behind D31's narrowing; NOT the granted scopes and NOT the vendor user id,
+ * which name the person and buy an operator nothing the id does not. The error goes through
+ * {@link reasonWithoutStatement}, because a `DrizzleQueryError`'s own message is the failed
+ * statement and every value bound to it — which here includes the scope list and the account.
+ */
+ try {
+ await store.completeAuthorizationAttempt({
+ actorId,
+ authId,
+ toolkit: fresh.toolkit,
+ account: fresh.account,
+ action: attempt.action,
+ scopes: settled.scopes,
+ authProvider: authProvider ?? "",
+ });
+ recorded = true;
+ } catch (error) {
+ recorded = await store
+ .authorizationAttemptCompleted({ actorId, authId })
+ .catch(() => false);
+ console.error(
+ JSON.stringify({
+ type: recorded
+ ? "arcade-completion-unacknowledged"
+ : "arcade-completion-not-recorded",
+ server: attempt.serverId,
+ action: attempt.action,
+ authorizationId: authId,
+ note: recorded
+ ? "A per-action consent was written down and the write then failed to report itself. The attempt row says it committed, so the person was sent on to the page that says so and nothing needs redoing."
+ : "A person completed a per-action consent at the vendor and this deployment could not write it down. The authorization named here is LIVE at the vendor with nothing here recording it; the person was told so, and the attempt row stands until its TTL.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ if (!recorded) return refusedWith("not-recorded");
+ }
+
+ return context.redirect(landing, 303);
+ } catch (error) {
+ /*
+ * AND THE BLANKET `catch` IS NO LONGER WHERE A LOST COMPLETION GOES QUIET. It stands over the
+ * reads that happen BEFORE the completion — the guard's session and role reads, the attempt
+ * read, and the refusals' own string building — so `not-recorded` is true of everything that
+ * reaches it: the completion above answers for itself, under its own `catch`, and never falls
+ * through to here still thinking it might have landed.
+ *
+ * `recorded` IS LOGGED RATHER THAN ASSUMED FALSE. The only step left after the completion is
+ * the redirect, and the sole way it can throw is a `Location` this deployment's own `appUrl`
+ * made unencodable — which would equally defeat the refusal built below, so there is no
+ * honest second answer to give here and the flag is what makes the claim checkable in a
+ * deployment rather than only in this comment.
+ *
+ * LOGGED AT ALL, which is the second half of what this leg was missing: every sibling on this
+ * route reports a failure it swallowed, and this one reported nothing. With the same
+ * restraint as the completion's own line above and nothing of the vendor's in it, through
+ * {@link reasonWithoutStatement} for the reason given there.
+ */
+ console.error(
+ JSON.stringify({
+ type: "arcade-verify-failed",
+ recorded,
+ note: "A check on the verifier raised instead of answering. The person was sent back to the list with a failure.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ return refusedWith("not-recorded");
+ }
+ });
}
diff --git a/server/src/plugins/route-groups/context.ts b/server/src/plugins/route-groups/context.ts
index 87635f5f8..c061485ce 100644
--- a/server/src/plugins/route-groups/context.ts
+++ b/server/src/plugins/route-groups/context.ts
@@ -1,9 +1,16 @@
import type { Hono, MiddlewareHandler } from "hono";
import type { BotAccessCheck } from "../../agents/profile-policy";
import type { AppVariables } from "../../auth/guards";
+import { BROKERED_PROFILE, type BrokerProvider } from "../access";
import type { AccountModeSwitch } from "../account-mode";
-import { brokerSentence, type ComposioBroker } from "../broker";
+import {
+ type AuthorizationFlow,
+ BrokerReturnUrlError,
+ brokerSentence,
+ type ComposioBroker,
+} from "../broker";
import { vendorSentence } from "../composio";
+import type { AccountRef } from "../shared-accounts";
import type { SharedUseStore } from "../shared-use-store";
import type { PluginStore } from "../store";
@@ -70,15 +77,261 @@ export function brokerRefusal(
}
/**
- * What a reader is told when Composio would not answer with its directory.
+ * A return address this deployment could not use, said once where an operator will read it.
*
- * One constant because two routes make that call — browsing the catalogue and enabling an app out of
+ * THE REMEDY AND THE READER HAD COME APART, WHICH IS WHAT THIS CLOSES. Both routes that check a
+ * return address are `requireUser`, so the sentence they answer with reaches somebody who pressed
+ * Connect — and `OPENBOT_APP_URL` is a variable they cannot set, named beside a vendor they may not
+ * even be on. {@link BrokerReturnUrlError} answers them with neither now, which would leave the
+ * operator's half nowhere at all if it were not written here: a misconfigured address would be
+ * invisible to the one person who can correct it.
+ *
+ * A LOG AND NOT A RESPONSE, because that is the difference between the two readers. The log is the
+ * operator's screen, and the sentence it carries is the error's own — written in `./broker`, naming
+ * the ACTIVE provider, and quoting no address, which is the promise that makes it safe to write
+ * down at all. Anything else thrown past this is somebody else's to explain, so it is ignored here
+ * rather than logged generically.
+ */
+export function logReturnUrlRefusal(error: unknown, serverId: string): void {
+ if (!(error instanceof BrokerReturnUrlError)) return;
+ console.error(
+ JSON.stringify({
+ type: "broker-return-url-unusable",
+ serverId,
+ note: error.operatorMessage,
+ }),
+ );
+}
+
+/**
+ * What a reader is told when the active broker would not answer with its directory.
+ *
+ * One function because two routes make that call — browsing the catalogue and enabling an app out of
* it — and a reader meeting the same failure through two doors should not meet two sentences. It
* names the setting rather than quoting it: a key that was pasted with a space in it, or one for
- * another project, is the likeliest reason Composio will not talk to this deployment at all.
+ * another project, is the likeliest reason the vendor will not talk to this deployment at all.
+ *
+ * THE VENDOR AND ITS SETTING COME OFF THE ACTIVE PROVIDER'S PROFILE, because the client shows the
+ * server's sentence in preference to its own fallback: a sentence that spelled one broker was read
+ * by an operator of the other.
+ */
+export function directoryUnavailable(provider: BrokerProvider): string {
+ const { vendor, envVar } = BROKERED_PROFILE[provider];
+ return `${vendor} would not answer with its app directory, and said nothing about why. Check that ${envVar} is this project's key, and check ${vendor}'s status if it persists.`;
+}
+
+/**
+ * Where a browser with no session is sent when it lands on the verifier, carrying what it arrived
+ * with.
+ *
+ * ABSOLUTE ON THE APP'S ORIGIN, for the reason {@link connectedAccountsUrlFor} is: the verifier
+ * lands on the API and the sign-in screen is a page, and locally those are two different ports — a
+ * relative `Location` would resolve against this server, which serves no pages at all. A deployment
+ * that serves both from one origin has no `appUrl`, and relative is correct there.
+ *
+ * THE HANDLE TRAVELS AS ITSELF AND NOT AS A RETURN ADDRESS. A `next=` parameter holding a URL is a
+ * redirect target somebody else gets a say in; this carries the one opaque value the return leg
+ * arrived with, so signing back in as the same person has everything it needs to resume and nothing
+ * it could be pointed elsewhere with. Encoded, because it is somebody else's id and this deployment
+ * does not know what is in it.
+ */
+export function signInUrlFor(
+ appUrl: string | undefined,
+ flowId: string,
+): string {
+ const base = `${appUrl?.replace(/\/+$/, "") ?? ""}/sign`;
+ return flowId ? `${base}?flow_id=${encodeURIComponent(flowId)}` : base;
+}
+
+/**
+ * WHY THE VERIFIER REFUSED, AS AN OPAQUE CODE — and the CLOSED SET of them.
+ *
+ * THE CODE TRAVELS AND THE SENTENCE DOES NOT. Each of these is rendered by the connected-accounts
+ * list, which holds the words; a message rendered from a query string is a message anybody can
+ * author on this deployment's own origin, and the code is also what keeps the copy in ONE place
+ * rather than in two that drift. The parameter is `?authorized=`, a channel of its own: `?connected=`
+ * belongs to the Composio OAuth callback and is neither reused nor widened here.
+ *
+ * ASSERTED AS A SET so an eleventh cannot be added without a test going red, and so the app's own map
+ * can be held to exactly the same list.
+ */
+export const AUTHORIZATION_REFUSALS = [
+ // This deployment reaches no broker at all, so there is no return leg to verify.
+ "no-broker",
+ // The broker it reaches declares no correlated authorization flow.
+ "not-supported",
+ // The link arrived without the part that says which authorization it finishes.
+ "missing-handle",
+ // Nothing USABLE of this person's is waiting: never started, expired, another session's, a mint
+ // that never answered with an id, or a row that cannot say whose account it is for.
+ "no-attempt",
+ // The vendor was asked and did not answer.
+ "vendor-unavailable",
+ // The link, or the authorization behind it, is not the one this person is holding.
+ "not-your-authorization",
+ // The consent has not finished at the vendor yet. The attempt is LEFT STANDING for a retry.
+ "not-finished",
+ // The vendor says the authorization did not complete.
+ "authorization-failed",
+ // The app's account context moved on while the consent was open (D28).
+ "context-changed",
+ // This deployment could not write what it had, so it wrote none of it.
+ "not-recorded",
+] as const;
+
+export type AuthorizationRefusal = (typeof AUTHORIZATION_REFUSALS)[number];
+
+/** Whether two account references name the same account, holder included. */
+export function sameAccount(left: AccountRef, right: AccountRef): boolean {
+ if (left.vendorUserId !== right.vendorUserId) return false;
+ if (left.holder === "person") {
+ return right.holder === "person" && left.userId === right.userId;
+ }
+ return right.holder === "deployment";
+}
+
+/**
+ * A BROKER CALL THAT DID NOT ANSWER INSIDE ITS BOUND, AND CARRYING NOTHING OF THE VENDOR'S (D31).
+ *
+ * The message names this deployment's own bound and the thrown object has no `cause`, deliberately:
+ * a call that times out on the return leg is a call whose answer would have carried a LIVE provider
+ * access token and a full profile, and the one thing certain about a half-read answer is that
+ * nobody has narrowed it yet. So there is nothing here to attach, log or quote — which is the same
+ * promise {@link brokerRefusal} makes about the vendor's own errors, kept by having no vendor
+ * material to begin with.
+ *
+ * It is an ordinary `Error` to every reader of it: `brokerSentence` does not author it and
+ * `vendorSentence` finds no nested body, so every call site below answers it with its OWN generic
+ * sentence and a 502 — "we asked and got nothing back", which is exactly what happened.
+ *
+ * NOT EXPORTED, because nothing outside this file should be branching on it: every exit it can
+ * reach is already one of this file's own refusals, and a caller matching on the class would be
+ * re-deciding what the catch blocks here have decided.
*/
-export const DIRECTORY_UNAVAILABLE =
- "Composio would not answer with its app directory, and said nothing about why. Check that COMPOSIO_API_KEY is this project's key, and check Composio's status if it persists.";
+class VendorTimeoutError extends Error {
+ constructor(afterMs: number) {
+ super(`The broker did not answer within ${afterMs}ms.`);
+ this.name = "VendorTimeoutError";
+ }
+}
+
+/**
+ * How long ONE outbound call to the broker may take before this deployment stops waiting on it.
+ *
+ * EVERY CALL OUT OF THIS FILE GOES THROUGH {@link withVendorDeadline}, and that is the invariant
+ * rather than the number. A bound on how MANY times something is attempted is not a bound at all
+ * while each attempt is unbounded: a vendor that accepts the connection and then never writes a
+ * byte holds the socket, the handler and the browser open for as long as it cares to, and no retry
+ * limit, poll count or loop condition is ever reached because the first wait never ends.
+ */
+const VENDOR_CALL_TIMEOUT_MS = 10_000;
+
+/**
+ * The same bound on ONE return-leg status poll, and tighter because a browser is held open on it.
+ *
+ * Five polls at two seconds with four one-second pauses is a worst case of FOURTEEN seconds for the
+ * whole wait, and that arithmetic is the point of stating both numbers here.
+ */
+export const STATUS_CALL_TIMEOUT_MS = 2_000;
+
+/**
+ * At most five polls, pausing BETWEEN them — so four pauses, not five. NEVER the SDK's unbounded
+ * `waitForCompletion`.
+ */
+const STATUS_POLLS = 5;
+const STATUS_PAUSE_MS = 1000;
+
+/**
+ * One outbound vendor call under a deadline, with an `AbortSignal` for the seams that take one.
+ *
+ * THE SIGNAL IS THE HALF THAT MATTERS UPSTREAM AND THE RACE IS THE HALF THAT MATTERS HERE. The race
+ * is what lets the handler answer and the browser go; aborting is what stops this deployment from
+ * holding a socket open against a vendor it has already given up on, and the seams that can carry
+ * a signal take it (see {@link AuthorizationFlow}). A seam that cannot is still bounded by the
+ * race — a timeout is a timeout to the person waiting either way — and its call is simply
+ * abandoned rather than cancelled.
+ *
+ * THE TIMER IS CLEARED ON BOTH OUTCOMES. A pending timer on a call that answered in time would keep
+ * an event loop alive for the rest of the bound, which in a test run is a suite that will not exit
+ * and in a deployment is a handler that has returned still holding a reason not to.
+ *
+ * NOTHING IS RETRIED HERE. A deadline that retried would be the very shape this fix exists to
+ * remove, one layer up.
+ */
+export function withVendorDeadline(
+ call: (signal: AbortSignal) => Promise,
+ afterMs: number = VENDOR_CALL_TIMEOUT_MS,
+): Promise {
+ const controller = new AbortController();
+ let timer: ReturnType | undefined;
+ const deadline = new Promise((_resolve, reject) => {
+ timer = setTimeout(() => {
+ controller.abort();
+ reject(new VendorTimeoutError(afterMs));
+ }, afterMs);
+ });
+ /*
+ * A CALL THAT THROWS BEFORE IT RETURNS A PROMISE IS A REJECTION, NOT AN ESCAPE. The deadline is
+ * already armed by this line, so invoking `call` bare inside the race would let a synchronous
+ * throw — `brokerReturnUrl` refusing a scheme-less `OPENBOT_APP_URL` while the callback builds its
+ * argument is the one that happens — leave this function before the race and its `.finally`
+ * exist: the timer stays armed, aborts nothing anybody is waiting on and rejects `deadline` with
+ * no one subscribed, and a caller's `.catch(...)` on the returned promise is skipped entirely.
+ * Calling it inside an executor turns that throw into this promise's rejection, still
+ * synchronously and still exactly once.
+ */
+ const answer = new Promise((resolve) => resolve(call(controller.signal)));
+ /*
+ * `Promise.race` subscribes to both, so a call that answers AFTER the deadline has its rejection
+ * observed here and never surfaces as an unhandled one. Its answer is dropped unread, which is
+ * also the D31-safe thing to do with it.
+ */
+ return Promise.race([answer, deadline]).finally(() => {
+ if (timer !== undefined) clearTimeout(timer);
+ });
+}
+
+/**
+ * Where one authorization has got to, waited for with a BOUND ON THE WHOLE WAIT AND ON EACH CALL.
+ *
+ * `pending` is the only state worth asking again about, and five polls at two seconds each with a
+ * one-second pause between them — at most fourteen seconds, and the pauses are BETWEEN the polls,
+ * so four of them and not five — is the whole of the patience this has. A timeout is neither a
+ * success nor a failure, so what comes back is the last answer read and the caller decides; nothing
+ * here writes, retries forever or blocks a browser on a vendor that has stopped answering.
+ *
+ * THE POLL COUNT ALONE NEVER BOUNDED ANYTHING. It bounded how many times `flow.status` was CALLED,
+ * while each call waited as long as the vendor liked — so a vendor that accepted the connection and
+ * never answered held this request open forever, which is the one case the paragraph above claims
+ * to prevent. {@link withVendorDeadline} is what makes the claim true, and a poll that spends its
+ * own bound THROWS rather than counting: a vendor that will not answer in two seconds is a vendor
+ * that is not answering, and the caller's `vendor-unavailable` refusal leaves the attempt row
+ * standing exactly as the exhausted-polls refusal does.
+ *
+ * WHAT COMES BACK IS THE SEAM'S THREE FIELDS AND NOTHING MORE (D31). The vendor's own status reply
+ * carries a live provider access token and the person's full profile; the adapter drops them, and
+ * this function must never widen what it hands on, log it, or attach it as a `cause`.
+ */
+export async function settledStatus(
+ flow: AuthorizationFlow,
+ id: string,
+): Promise>> {
+ const poll = () =>
+ withVendorDeadline(
+ (signal) => flow.status(id, { signal }),
+ STATUS_CALL_TIMEOUT_MS,
+ );
+ let answer = await poll();
+ for (
+ let polls = 1;
+ polls < STATUS_POLLS && answer.status === "pending";
+ polls += 1
+ ) {
+ await new Promise((resolve) => setTimeout(resolve, STATUS_PAUSE_MS));
+ answer = await poll();
+ }
+ return answer;
+}
/** What the connect flow needs that the store does not hold. See `createPluginRoutes`. */
export type PluginConnectOptions = {
@@ -123,8 +376,14 @@ export type PluginRouteContext = {
requireUser: MiddlewareHandler<{ Variables: AppVariables }>;
canUseBot: BotAccessCheck;
connect: PluginConnectOptions | undefined;
- composio: { broker: ComposioBroker } | undefined;
+ composio: { broker: ComposioBroker; provider: BrokerProvider } | undefined;
shared: { modes: AccountModeSwitch; use: SharedUseStore } | undefined;
+ /** The provider this deployment's brokered rows belong to. See `createPluginRoutes`. */
+ activeProvider: BrokerProvider;
+ /** {@link activeProvider}'s name as the sentences on this layer spell it. */
+ activeVendor: string;
+ /** Which app a brokered row is, read by {@link activeProvider}'s own parser. */
+ appOfUrl: (url: string) => string | null;
actorEmail: (context: { var: AppVariables }) => string;
skillActor: (context: { var: AppVariables }) => {
id: string;
diff --git a/server/src/plugins/route-groups/servers.ts b/server/src/plugins/route-groups/servers.ts
index 9c51fdab2..0eb831c6d 100644
--- a/server/src/plugins/route-groups/servers.ts
+++ b/server/src/plugins/route-groups/servers.ts
@@ -2,7 +2,7 @@ import { requireAdmin } from "../../auth/guards";
import { reasonWithoutStatement } from "../../db/query-failure";
import { brokerSentence, schemeKind } from "../broker";
import { CATALOGUE } from "../catalogue";
-import { toolkitOf, vendorSentence } from "../composio";
+import { vendorSentence } from "../composio";
import { redirectUriFor } from "../oauth";
import { asAccountMode } from "../shared-accounts";
import { asSharedUseApproval, type SharedUseApproval } from "../shared-use";
@@ -22,6 +22,9 @@ export function mountServerRoutes({
connect,
composio,
shared,
+ activeProvider,
+ activeVendor,
+ appOfUrl,
actorEmail,
skillActor,
}: PluginRouteContext) {
@@ -61,6 +64,46 @@ export function mountServerRoutes({
* screen needs is whether to draw the app directory, which is a yes or a no.
*/
composioConfigured: Boolean(composio),
+ /*
+ * Whether this deployment's broker authorizes ONE ACTION AT A TIME, as a declared capability.
+ *
+ * A PRESENCE TEST ON A VALUE THE BROKER DECLARED, computed from the same expression the two
+ * routes that run that flow gate on — `/servers/:id/authorize` and the `/arcade/verify`
+ * return leg both read `composio.broker.authorizations`. The screen that draws the per-action
+ * grant list used to gate on `provider === "arcade"` instead, so the screen and the routes
+ * keyed on different facts: a broker named arcade reached with `authorizations` null would
+ * have drawn the list and 503'd every Authorize press. See {@link
+ * ConnectedAppBroker.authorizations} and `store.ts`'s gate comment, which records a defect of
+ * exactly that shape.
+ *
+ * A capability, never a name: what the screen asks is what the broker can DO, and two
+ * providers are free to share an answer. `provider` beside it stays a NAME and answers a
+ * different question — which vendor to put in copy.
+ *
+ * False on Composio, whose auth config fixes one state for a whole toolkit, so every Composio
+ * render is unchanged.
+ */
+ authorizesPerAction: Boolean(composio?.broker.authorizations),
+ /*
+ * WHICH vendor brokers this deployment's apps, as a name.
+ *
+ * Handed in with the broker, never read off it: the broker declares what it can do, not who
+ * it is, and a name sniffed from it would be the comparison the store argues against.
+ *
+ * A screen that FILTERS by brokered-ness needs no provider name. One provider holds rows at a
+ * time, so a predicate over a row's provenance is enough. A screen that NAMES a vendor
+ * ("Browse Composio", the `/admin/plugins/composio` route, the key's setting, "Reached
+ * through Composio") cannot derive that name from a row, and this is the only thing that
+ * answers it. It is separate from `composioConfigured`, which asks whether the directory can
+ * be browsed at all.
+ *
+ * A NAME, never a key and never a capability. The Plugins page is reachable by any signed-in
+ * person, so nothing about the vendor key (its presence, shape or anything derived from it)
+ * travels with this. Composio when no broker is configured, so that deployment is unchanged —
+ * read from {@link activeProvider}, which is also what the brokered reads below parse with, so
+ * the name this page shows and the parser those routes use cannot come apart.
+ */
+ provider: activeProvider,
servers: await store.listServers(),
// Scoped: the deployment's skills plus this person's own. An administrator sees them all.
skills: await store.listSkills(skillActor(context)),
@@ -358,7 +401,7 @@ export function mountServerRoutes({
JSON.stringify({
type: "mcp-server-not-removed",
server: serverId,
- note: "Removing a server failed for a reason neither this deployment nor Composio put a sentence to. The administrator was answered 502 with the generic sentence, and the server row is still there.",
+ note: `Removing a server failed for a reason neither this deployment nor ${activeVendor} put a sentence to. The administrator was answered 502 with the generic sentence, and the server row is still there.`,
error: reasonWithoutStatement(error),
}),
);
@@ -366,12 +409,36 @@ export function mountServerRoutes({
const refusal = brokerRefusal(
error,
- "Removing this app did not finish, and neither this deployment nor Composio said why, so the app is still here rather than gone with grants of its own left live at the vendor. Press Remove again: every account is withdrawn at Composio before anything here is deleted, so repeating it is safe and asks only for what is left. Check this deployment's Composio key if it persists.",
+ `Removing this app did not finish, and neither this deployment nor ${activeVendor} said why, so the app is still here rather than gone with grants of its own left live at the vendor. Press Remove again: every account is withdrawn at ${activeVendor} before anything here is deleted, so repeating it is safe and asks only for what is left. Check this deployment's ${activeVendor} key if it persists.`,
);
return context.json({ error: refusal.error }, refusal.status);
}
- if (wasShared)
- await shared?.modes.forgetRule(serverId, context.var.actor.id);
+ /*
+ * THE SAME SHAPE AS THE MINT'S RECORD-AFTER-THE-FACT BELOW, AND GUARDED FOR THE SAME REASON.
+ * `removeServer` has already returned, which means every account was withdrawn AT THE VENDOR
+ * and every row here is gone — none of it undoable, and none of it repeatable either: a second
+ * Remove finds no app. Unhandled, a fault tidying up the one leftover ask-before-write rule
+ * answered that completed removal with a bodyless 500, so the administrator was told the
+ * removal failed and had no press left to make.
+ *
+ * SO THE ANSWER STAYS `ok`, BECAUSE THE REMOVAL REALLY DID HAPPEN, and the leftover is named on
+ * the console instead: a revoked-rule row that outlives the app it guarded, which an
+ * administrator can retire from the rules screen and which grants nobody anything meanwhile.
+ */
+ if (wasShared) {
+ try {
+ await shared?.modes.forgetRule(serverId, context.var.actor.id);
+ } catch (error) {
+ console.error(
+ JSON.stringify({
+ type: "shared-rule-not-forgotten",
+ server: serverId,
+ note: "An app was removed, at the vendor and here, and its ask-before-write rule could not be retired afterwards. The administrator was answered ok, because the removal is done and cannot be repeated; the rule is still standing.",
+ error: reasonWithoutStatement(error),
+ }),
+ );
+ }
+ }
return context.json({ ok: true });
});
@@ -398,7 +465,7 @@ export function mountServerRoutes({
return context.json({ error: "Choose personal or shared." }, 400);
const serverId = context.req.param("id");
const row = await store.serverAddress(serverId);
- if (!row || !toolkitOf(row.url)) {
+ if (!row || !appOfUrl(row.url)) {
return context.json(
{ error: "That app is not reached through a broker." },
400,
diff --git a/server/src/plugins/routes.ts b/server/src/plugins/routes.ts
index b3cebe4ba..e78ce8b17 100644
--- a/server/src/plugins/routes.ts
+++ b/server/src/plugins/routes.ts
@@ -1,6 +1,7 @@
import { Hono, type MiddlewareHandler } from "hono";
import type { BotAccessCheck } from "../agents/profile-policy";
import type { AppVariables } from "../auth/guards";
+import { BROKERED_PROFILE, type BrokerProvider, toolkitOfFor } from "./access";
import type { AccountModeSwitch } from "./account-mode";
import type { ComposioBroker } from "./broker";
import { mountComposioAppRoutes } from "./route-groups/composio-apps";
@@ -16,7 +17,11 @@ import { mountSkillRoutes } from "./route-groups/skills";
import type { SharedUseStore } from "./shared-use-store";
import type { PluginStore } from "./store";
-export type { ConnectingPersonCheck } from "./route-groups/context";
+export type {
+ AuthorizationRefusal,
+ ConnectingPersonCheck,
+} from "./route-groups/context";
+export { AUTHORIZATION_REFUSALS } from "./route-groups/context";
/**
* The Plugins surface: what this deployment has added, and which Bots may use it.
@@ -72,7 +77,7 @@ export function createPluginRoutes(
* trap `connect` documents applies here with one more argument in it — every parameter from that
* position on is optional, so a misplaced one typechecks and quietly does nothing.
*/
- composio?: { broker: ComposioBroker },
+ composio?: { broker: ComposioBroker; provider: BrokerProvider },
/**
* Who may use an app's one shared account, and the switch that makes an app Shared or Personal.
*
@@ -84,6 +89,80 @@ export function createPluginRoutes(
) {
const routes = new Hono<{ Variables: AppVariables }>();
+ /**
+ * WHICH broker this deployment's rows are reached through, as a name, read once for the whole
+ * layer.
+ *
+ * HANDED IN WITH THE BROKER AND NEVER SNIFFED OFF IT. The broker declares what it can DO —
+ * `grantIsLocal`, `authorizations` — deliberately not who it is, so a vendor name derived from it
+ * would be the comparison `store.ts` argues against, made against a capability that two providers
+ * are free to share.
+ *
+ * TAKEN FROM THE STORE, BECAUSE THE STORE'S ROWS ARE WHAT IS BEING NAMED. It used to be read off
+ * the broker bag with `?? "composio"` behind it, and that `??` was a second answer to a question
+ * `server/src/index.ts` had already answered. The two came apart exactly where it hurt: a
+ * deployment with both API keys set and `OPENBOT_BROKER_PROVIDER` unset — legal, and what a
+ * migration sits in — is wired with NO broker, so the bag is absent, so every `arcade://` row of
+ * that deployment was parsed with Composio's parser. The account-mode switch refused those rows
+ * as "not reached through a broker", the directory called enabled apps not enabled, connect fell
+ * past its brokered branch in silence, and the operator was told to set COMPOSIO_API_KEY. The
+ * store was built with the one name the deployment decided; reading it here means the parser
+ * these routes use, the filter the store queries with and the name the page shows are one value.
+ *
+ * NO BROKER IS STILL NOT THE CASE THIS DECIDES. Rows outlive a key being unset, and the
+ * Personal ⇄ Shared switch is wired from the shared bag rather than from the broker, so it
+ * answers for such a row today — now with that row's own parser rather than Composio's.
+ */
+ const activeProvider: BrokerProvider = store.brokerProvider;
+ /*
+ * Said at construction, not at the first request. A store without this field is a stand-in rather
+ * than a store, and one that got this far would have every brokered read dispatch on `undefined`
+ * — four routes answering wrongly, none of them raising. Typed as present, so this catches only a
+ * cast; it refuses rather than filling in a vendor, which is the whole of this file's bug.
+ */
+ if (!activeProvider) {
+ throw new Error(
+ "createPluginRoutes was given a plugin store with no brokerProvider. The store names the provider its rows belong to; the routes never guess one.",
+ );
+ }
+
+ /**
+ * The ACTIVE broker's name, as the sentences on this layer spell it.
+ *
+ * ONE READING FOR EVERY SENTENCE, AND NEVER A LITERAL. Every refusal below used to spell
+ * "Composio" into the prose — the app that would not be removed, the app that would not be
+ * enabled, the six ways a key app's connect fails, the consent with nowhere to send anybody, and
+ * the confirm, re-check and disconnect tails. On an Arcade deployment each of them named a vendor
+ * this deployment holds no key for and never dials, and sent the reader to check a key that does
+ * not exist. The name comes off {@link activeProvider}'s profile, which is also what the brokered
+ * reads below parse with, so the vendor a sentence names and the vendor a route actually reached
+ * cannot come apart.
+ *
+ * THE PROFILE RATHER THAN A TERNARY, because {@link BROKERED_PROFILE} is where a provider's
+ * vocabulary is written down once — a second spelling here is the second home the record exists
+ * to prevent, and a provider added to it without being added here would typecheck.
+ */
+ const activeVendor = BROKERED_PROFILE[activeProvider].vendor;
+
+ /**
+ * Which app a brokered row is, read by the parser belonging to the broker this deployment runs.
+ *
+ * ONE PARSER PER PROVIDER, AND THEY ARE NOT INTERCHANGEABLE — the argument is {@link toolkitOfFor}'s
+ * and is not repeated here. What this layer adds is that all four reads below used to call
+ * Composio's parser by name, and that parser answers null for `arcade://Gmail` by design. Four
+ * reads, four different wrong answers on an Arcade deployment, and not one of them a failure or a
+ * log line: the account-mode switch refused a brokered app as "not reached through a broker", the
+ * directory reported every enabled app as not enabled, the connect route fell past its brokered
+ * branch in silence, and `brokeredAccountFor` — step one of confirm, re-check and disconnect —
+ * refused a connected app before any account was resolved.
+ *
+ * A CLOSURE RATHER THAN THE DISPATCH SPELLED FOUR TIMES, which is how `shared-use-store.ts` holds
+ * the same thing: the provider is read in one place, so a fifth read added below cannot be the one
+ * that forgets to pass it.
+ */
+ const appOfUrl = (url: string): string | null =>
+ toolkitOfFor(activeProvider, url);
+
const actorEmail = (context: { var: AppVariables }) =>
context.var.actor?.email ?? "unknown";
@@ -120,6 +199,9 @@ export function createPluginRoutes(
connect,
composio,
shared,
+ activeProvider,
+ activeVendor,
+ appOfUrl,
actorEmail,
skillActor,
skillRefusal,
diff --git a/server/src/plugins/shared-accounts.ts b/server/src/plugins/shared-accounts.ts
index 3792413f9..c68e33563 100644
--- a/server/src/plugins/shared-accounts.ts
+++ b/server/src/plugins/shared-accounts.ts
@@ -1,4 +1,5 @@
import { randomBytes } from "node:crypto";
+import { brokeredProvenance } from "./access";
import { type Decides, type SchemeKind, schemeKind } from "./broker";
/**
@@ -20,10 +21,86 @@ export type AccountRef =
| { holder: "person"; userId: string; vendorUserId: string }
| { holder: "deployment"; vendorUserId: string };
+/**
+ * A PERSON'S ACCOUNT, AND WHY ITS TWO IDS ARE ONE VALUE.
+ *
+ * `userId` and `vendorUserId` are different CONCEPTS — who the account belongs to here, and the
+ * name the vendor files it under — and they are kept as two fields because the deployment holder
+ * proves they can differ: its vendor id is minted by {@link mintDeploymentVendorUserId} and has no
+ * local user at all. FOR A PERSON THEY ARE THE SAME VALUE ON PURPOSE, for both providers:
+ *
+ * - Neither vendor issues a user handle. Composio's `user_id` and Arcade's `user_id` are both a
+ * string the CALLER chooses and the vendor files connections under, so "the vendor's id for this
+ * person" is whatever this deployment sends, and what it sends is the person's own id.
+ * - Every person connection that exists was made that way. Before shared accounts the Composio
+ * adapter was handed the local user id directly, and migration 0052 backfilled
+ * `vendor_user_id = user_id` for every person row it carried over. Sending anything else now
+ * would ask the vendor about an identity nobody connected under, and every existing connection
+ * would read as absent.
+ * - Arcade echoes the id back: a return leg's settled `userId` is compared to the attempt's stored
+ * `vendor_user_id`, which was minted from this ref.
+ *
+ * SO DO NOT "FIX" THE COLLAPSE BY DERIVING A DIFFERENT VENDOR ID HERE without migrating every
+ * stored person row and every vendor-side connection with it. And do not read the collapse as
+ * licence to use one field for the other: a store lookup by person matches `user_id`, a vendor call
+ * sends `vendorUserId`, and code that crosses them is only right by coincidence. This is the one
+ * place a person's ref is minted from a local id; a ref read back from a stored row goes through
+ * {@link asAccountRef} and carries the row's own `vendor_user_id`.
+ */
+export function personAccount(userId: string): AccountRef {
+ return { holder: "person", userId, vendorUserId: userId };
+}
+
export function asAccountMode(value: unknown): AccountMode | null {
return value === "personal" || value === "shared" ? value : null;
}
+/**
+ * A STORED `(holder, user_id)` PAIR READ BACK AS THE ACCOUNT IT NAMES, or `null` when it names no
+ * account this deployment may act in.
+ *
+ * THE POINT IS THE `null`, AND IT EXISTS BECAUSE THE OTHER READING IS SILENT. A row that says
+ * `person` and carries no user id was for years rebuilt as `{ holder: "deployment" }` by a ternary
+ * whose else-branch quietly absorbed it — and a reference is not a label: it is what a revoke, a
+ * grant read and a call are aimed at. Aimed one holder out, a disconnect somebody asked for ends
+ * the DEPLOYMENT's connection and leaves theirs live, or ends the deployment's on one person's
+ * behalf. Both failures look exactly like success from the browser that asked.
+ *
+ * THE TWO REFUSALS ARE THE DATABASE'S OWN CHECK, RESTATED WHERE TYPESCRIPT CAN SEE IT.
+ * `brokered_connections_holder_check` and `pending_authorizations_holder_check` both hold
+ * `(person AND user_id IS NOT NULL) OR (deployment AND user_id IS NULL)`, so for a row that came
+ * out of either table every refusal here is already impossible — which is the whole reason to
+ * refuse rather than to coerce. An impossible state answered with a plain `null` is a caller's
+ * refusal; the same state absorbed into the other holder is a wrong act with no trace. The empty
+ * string is refused with the null, because a CHECK for `IS NOT NULL` does not stop `''` and `''` is
+ * nobody's user id.
+ *
+ * `holder` IS TAKEN AS `string` ON PURPOSE. drizzle's `$type<>` is erased at runtime, so a column
+ * declared as the pair can still deliver a third word; a parameter typed as the union would let the
+ * compiler promise what only the CHECK enforces.
+ */
+export function asAccountRef(stored: {
+ holder: string;
+ userId: string | null;
+ vendorUserId: string;
+}): AccountRef | null {
+ if (stored.holder === "person") {
+ return stored.userId
+ ? {
+ holder: "person",
+ userId: stored.userId,
+ vendorUserId: stored.vendorUserId,
+ }
+ : null;
+ }
+ if (stored.holder === "deployment") {
+ return stored.userId
+ ? null
+ : { holder: "deployment", vendorUserId: stored.vendorUserId };
+ }
+ return null;
+}
+
export type AccountAnswer =
| { kind: "none" }
| { kind: "person"; userId: string }
@@ -39,7 +116,7 @@ export function accountFor(
},
credentialActorId: string,
): AccountAnswer {
- if (row.provenance !== "composio") return { kind: "none" };
+ if (!brokeredProvenance(row.provenance)) return { kind: "none" };
const kind = schemeKind(row.authScheme);
type _AccountForDecides = Decides<
diff --git a/server/src/plugins/shared-use-store.ts b/server/src/plugins/shared-use-store.ts
index c63c345ad..1ac9f9609 100644
--- a/server/src/plugins/shared-use-store.ts
+++ b/server/src/plugins/shared-use-store.ts
@@ -20,7 +20,7 @@ import {
teamBotPublications,
users,
} from "../db/schema";
-import { toolkitOf } from "./composio";
+import { BROKERED_PROFILE, type BrokerProvider, toolkitOfFor } from "./access";
import {
type ActorFacts,
type ApprovalMember,
@@ -73,12 +73,38 @@ export type SharedUseGate = (input: {
const sameApproval = (left: SharedUseApproval, right: SharedUseApproval) =>
covers(left, right) && covers(right, left);
-export function createSharedUseStore(database: Database) {
+/**
+ * The url scheme a broker's apps live at, read from the one record that holds it.
+ *
+ * DERIVED HERE AS `${provider}://` UNTIL {@link BROKERED_PROFILE} BECAME REACHABLE. That derivation
+ * was right about every scheme there is and still a second source of truth: the store mints its rows
+ * from the record, this read them back from a rule, and nothing held the two in step beyond the
+ * happy fact that a broker's scheme is its own name. A third broker that spelled its scheme any
+ * other way would have had its rows written at one address and looked for at another, silently, in
+ * the gate that decides who may use a shared account. One table, one spelling.
+ */
+const schemeOf = (provider: BrokerProvider) =>
+ BROKERED_PROFILE[provider].scheme;
+
+/**
+ * `provider` IS REQUIRED, with no default, for the reason `PluginStoreOptions.brokerProvider` is:
+ * this store has to read urls with the same parser, at the same scheme, as the store whose gate
+ * consults the approvals filed here. A default would let the two disagree silently, which is the
+ * failed-open shape the canonical-url rule below exists to prevent. The caller passes the one value
+ * the plugin store is given.
+ */
+export function createSharedUseStore(
+ database: Database,
+ provider: BrokerProvider,
+) {
const roles = createRoleRepository(database);
+ const scheme = schemeOf(provider);
+ const appOfUrl = (url: string) => toolkitOfFor(provider, url);
+ const canonicalUrlOf = (toolkit: string) => `${scheme}${toolkit}`;
/*
* EVERY ROW AT AN APP'S CANONICAL URL, LOWEST ID FIRST — THE ANSWERING ROW IS THE HEAD. Several
- * `mcp_servers` rows may point at one `composio://` url (the url has no unique index,
+ * `mcp_servers` rows may point at one `` url (the url has no unique index,
* deliberately), and the store's `brokeredAppRowsAt` names the row that answers for the app as
* the first one SQL's `order by id` yields at that url. This is the same rule spelled with the
* same `order by`, so the deployment's collation decides here exactly as it decides there — a
@@ -86,19 +112,19 @@ export function createSharedUseStore(database: Database) {
* than imported because the store's helper is a closure inside `createStore`, and this store is
* built from the database alone.
*
- * "AT THE URL" MEANS THE CANONICAL ONE, `composio://${toolkitOf(url)}`, NOT THE DIALLED ROW'S OWN
- * url string. `toolkitOf` (`./composio`) trims whitespace and a trailing slash before it reads
- * the slug, so a row stored as `composio://github/` or `composio://github ` names the toolkit
- * `github` exactly as `composio://github` does — and the store's own gate (`brokeredAppRow`,
+ * "AT THE URL" MEANS THE CANONICAL ONE, `${scheme}${appOfUrl(url)}`, NOT THE DIALLED ROW'S OWN
+ * url string. `appOfUrl` (the active provider's own parser, via `toolkitOfFor`) trims whitespace and a trailing slash before it reads
+ * the slug, so a row stored as `github/` or `github ` names the toolkit
+ * `github` exactly as `github` does — and the store's own gate (`brokeredAppRow`,
* `accountRefFor`) only ever looks rows up at the trimmed, canonical address. Grouping by the raw
* stored url instead, as this used to, made such a row an app of its own: an approval granted or
* read through it could never be found by the gate, which asks at the canonical url and nowhere
* else. So THIS is "the same rule" the comment above claims — same `order by`, same address the
* gate itself resolves to.
*
- * A row whose url is not `composio://` at all, or whose slug `toolkitOf` cannot parse, is an app
+ * A row whose url is not at the provider's scheme at all, or whose slug `appOfUrl` cannot parse, is an app
* of its own and is the only row in its answer — the same two cases the store's own gate lets
- * stand rather than refuses (`accountRefFor` reads `toolkitOf` only for a `composio` provenance,
+ * stand rather than refuses (`accountRefFor` reads `appOfUrl` only for a row provenanced at the active provider,
* and a null result there is read as "not brokered", not as a refusal).
*
* NO ROW AT THE CANONICAL URL is answered by falling back to the dialled `serverId` itself, which
@@ -122,9 +148,9 @@ export function createSharedUseStore(database: Database) {
.where(eq(mcpServers.id, serverId))
.limit(1);
const url = row?.url ?? null;
- const toolkit = url ? toolkitOf(url) : null;
+ const toolkit = url ? appOfUrl(url) : null;
if (!toolkit) return { appId: serverId, url, ids: [serverId] };
- const canonical = `composio://${toolkit}`;
+ const canonical = canonicalUrlOf(toolkit);
const siblings = await database
.select({ id: mcpServers.id })
.from(mcpServers)
@@ -371,9 +397,9 @@ export function createSharedUseStore(database: Database) {
* url to its answering row, and the mode, the title and the id returned are that row's. A Bot
* holding tools through two rows at one url holds one app and is listed once.
*
- * "ITS URL" IS THE CANONICAL ONE A GRANTED ROW RESOLVES TO THROUGH `toolkitOf`, same as
+ * "ITS URL" IS THE CANONICAL ONE A GRANTED ROW RESOLVES TO THROUGH `appOfUrl`, same as
* `rowsOfApp` above, and not the row's own stored url compared by prefix. A granted row stored as
- * `composio://github/` is the toolkit `github` exactly as `composio://github` is, and grouping it
+ * `github/` is the toolkit `github` exactly as `github` is, and grouping it
* by its raw url instead left it unmatched against the answering row at the trimmed address — so
* a Bot holding the app only through such a row was listed under a row of its own, or dropped,
* rather than under the one row the gate actually answers for the app.
@@ -395,13 +421,13 @@ export function createSharedUseStore(database: Database) {
.select({ id: mcpServers.id, url: mcpServers.url })
.from(mcpServers)
.where(inArray(mcpServers.id, serverIds));
- const toolkitOfRow = (url: string | null) => (url ? toolkitOf(url) : null);
+ const toolkitOfRow = (url: string | null) => (url ? appOfUrl(url) : null);
const canonicalUrls = [
...new Set(
granted
.map((row) => toolkitOfRow(row.url))
.filter((toolkit): toolkit is string => !!toolkit)
- .map((toolkit) => `composio://${toolkit}`),
+ .map(canonicalUrlOf),
),
];
const atUrls =
@@ -421,7 +447,7 @@ export function createSharedUseStore(database: Database) {
granted.map((row) => {
const toolkit = toolkitOfRow(row.url);
if (!toolkit) return row.id;
- return answeringAt.get(`composio://${toolkit}`) ?? row.id;
+ return answeringAt.get(canonicalUrlOf(toolkit)) ?? row.id;
}),
),
];
@@ -442,25 +468,25 @@ export function createSharedUseStore(database: Database) {
* to contain `_` and `%`, which a LIKE would read as wildcards and match a neighbouring app's
* grants by.
*
- * "ANY ROW AT ITS URL" HAS TO MEAN EVERY ROW `toolkitOf` RESOLVES TO THE SAME TOOLKIT, NOT ONLY
+ * "ANY ROW AT ITS URL" HAS TO MEAN EVERY ROW `appOfUrl` RESOLVES TO THE SAME TOOLKIT, NOT ONLY
* THE ONES `rowsOfApp` FOUND AT THE EXACT CANONICAL URL. `rowsOfApp`'s `ids` already covers the
* canonical rows plus the row actually dialled, but a grant can be made through a THIRD row —
- * neither the dialled one nor stored at the trimmed address, e.g. `composio:///` sitting
+ * neither the dialled one nor stored at the trimmed address, e.g. `/` sitting
* elsewhere in the table — and that row never surfaces from an exact `eq` on the canonical url. So
- * every `composio://` row is read once here and matched in JavaScript by `toolkitOf`, which is the
+ * every row at the provider's scheme is read once here and matched in JavaScript by `appOfUrl`, which is the
* one place that defines what "the same toolkit" means.
*/
async function botsHolding(serverId: string): Promise {
const { ids, url } = await rowsOfApp(serverId);
- const toolkit = url ? toolkitOf(url) : null;
+ const toolkit = url ? appOfUrl(url) : null;
let allIds = ids;
if (toolkit) {
- const composioRows = await database
+ const schemeRows = await database
.select({ id: mcpServers.id, url: mcpServers.url })
.from(mcpServers)
- .where(sql`${mcpServers.url} like 'composio://%'`);
- const matching = composioRows
- .filter((row) => row.url && toolkitOf(row.url) === toolkit)
+ .where(sql`${mcpServers.url} like ${`${scheme}%`}`);
+ const matching = schemeRows
+ .filter((row) => row.url && appOfUrl(row.url) === toolkit)
.map((row) => row.id);
allIds = [...new Set([...ids, ...matching])];
}
diff --git a/server/src/plugins/store.ts b/server/src/plugins/store.ts
index 556bedac7..e74eb9e77 100644
--- a/server/src/plugins/store.ts
+++ b/server/src/plugins/store.ts
@@ -9,9 +9,12 @@ import { skillMethods } from "./plugin-store/skills";
export type { CatalogueEntry } from "./catalogue";
export type {
AccessToken,
+ BrokeredDisconnectReason,
BrokeredProbe,
+ ConnectionFootprint,
GrantedPlugins,
OAuthClient,
+ PendingAuthorization,
PluginDecision,
PluginKind,
PluginStoreOptions,
@@ -25,6 +28,7 @@ export type {
export {
BROKERED_PROBE_OUTCOMES,
CatalogueEntryUnknownError,
+ connectionSiblings,
CustomServerRefusedError,
deploymentFaultSentence,
exchangeRefreshTokenOverHttp,
diff --git a/server/src/plugins/transport.ts b/server/src/plugins/transport.ts
index 7a7d8bc10..e926533ce 100644
--- a/server/src/plugins/transport.ts
+++ b/server/src/plugins/transport.ts
@@ -1,3 +1,4 @@
+import * as arcade from "./arcade";
import * as builtinRoutines from "./builtin-routines";
import * as composio from "./composio";
import * as driveRest from "./google-drive-rest";
@@ -122,12 +123,13 @@ export type TransportKind =
| "mcp"
| "google-drive-rest"
| "builtin-routines"
- | "composio";
+ | "composio"
+ | "arcade";
/**
* The kinds a CATALOGUE ENTRY may name, which is every one except the broker's.
*
- * CRITERION. `composio` is not writable in a reviewed entry, and the compiler is what says so.
+ * CRITERION. Neither broker's transport (`composio`, `arcade`) is writable in a reviewed entry, and the compiler is what says so.
*
* REASON. A brokered row is reached by an app slug read off its url and a per-person connection
* looked up by that slug; a catalogue entry has neither, and `accessFor` answers `toolkit: null`
@@ -141,13 +143,17 @@ export type TransportKind =
* `Exclude` rather than a hand-written second union, so a kind added above is offered to the
* catalogue automatically and only the broker stays out.
*/
-export type CuratedTransportKind = Exclude;
+export type CuratedTransportKind = Exclude<
+ TransportKind,
+ "composio" | "arcade"
+>;
const TRANSPORTS: Record = {
mcp,
"google-drive-rest": driveRest,
"builtin-routines": builtinRoutines,
composio,
+ arcade,
};
/**
diff --git a/server/tests/account-mode.integration.test.ts b/server/tests/account-mode.integration.test.ts
index 8caa2a883..01c79f5a1 100644
--- a/server/tests/account-mode.integration.test.ts
+++ b/server/tests/account-mode.integration.test.ts
@@ -32,10 +32,17 @@ const rules: {
}[] = [];
const broker = {
+ // This stub stands in for Composio, whose grant is held locally and which runs no
+ // correlated authorization flow; see `ConnectedAppBroker.grantIsLocal` and `.authorizations`.
+ grantIsLocal: true,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ authorizations: null,
+ grantsFor: async () => [],
listApps: async () => [],
ensureAuthConfig: async () => "standing",
deleteAuthConfig: async () => {},
- authorize: async () => ({ redirectUrl: "x" }),
+ authorize: async () => ({ redirectUrl: "x", authorizationId: null }),
isConnected: async () => true,
revoke: async ({ account }) => {
if (account.vendorUserId === failRevokeFor)
@@ -50,6 +57,7 @@ const broker = {
} satisfies ConnectedAppBroker;
const store = createPluginStore({
+ brokerProvider: "composio",
database,
auditStore: createAuditStore(database),
credentials: {} as never,
@@ -59,7 +67,7 @@ const store = createPluginStore({
deploymentId: "acme",
sharedUse: async () => ({ allowed: true }),
});
-const sharedUse = createSharedUseStore(database);
+const sharedUse = createSharedUseStore(database, "composio");
const modes = createAccountModeSwitch({
store,
sharedUse,
diff --git a/server/tests/agent-callback-token.test.ts b/server/tests/agent-callback-token.test.ts
index a73a6ad44..f80e640d5 100644
--- a/server/tests/agent-callback-token.test.ts
+++ b/server/tests/agent-callback-token.test.ts
@@ -420,6 +420,7 @@ describe("the tool-call route a callback token guards", () => {
undefined,
undefined,
{
+ brokerProvider: "composio" as const,
callTool: async () => {
throw thrown;
},
@@ -587,6 +588,7 @@ describe("the tool-call route a callback token guards", () => {
*/
function storeAnswering(result: { text: string; isError: boolean }) {
return {
+ brokerProvider: "composio" as const,
callTool: async () => ({ ...result, truncated: false }),
listForAgent: async () => ({
tools: [
diff --git a/server/tests/agent-handoff.test.ts b/server/tests/agent-handoff.test.ts
index 99a1cd5ff..97a98e6d9 100644
--- a/server/tests/agent-handoff.test.ts
+++ b/server/tests/agent-handoff.test.ts
@@ -4,10 +4,8 @@ import {
HANDOFF_KIND,
type HandoffCaps,
} from "../src/agents/handoff";
-import type {
- AgentProfile,
- AgentProfileStore,
-} from "../src/agents/profile-store";
+import type { AgentProfileStore } from "../src/agents/profile-store";
+import type { AgentProfile } from "../src/agents/profile-types";
import type { AuditStore } from "../src/audit";
import type { WorkQueue } from "../src/work/queue";
diff --git a/server/tests/arcade-adapter-authorize.test.ts b/server/tests/arcade-adapter-authorize.test.ts
new file mode 100644
index 000000000..c11ae4c85
--- /dev/null
+++ b/server/tests/arcade-adapter-authorize.test.ts
@@ -0,0 +1,208 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import Arcade, { APIError } from "@arcadeai/arcadejs";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { BrokerRefusalError } from "../src/plugins/broker";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+
+/** Prototype stubs BEFORE construction; `new Arcade({ apiKey })` opens no socket. */
+type Methods = Record unknown>;
+const protoOf = (pick: (c: Arcade) => object): Methods =>
+ Object.getPrototypeOf(
+ pick(new Arcade({ apiKey: "never-dialled" })),
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ on: Methods,
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+) {
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+ useArcadeClient(null);
+});
+
+const account: AccountRef = {
+ holder: "person",
+ userId: "u1",
+ vendorUserId: "vendor-u1",
+};
+
+describe("arcade adapter authorize", () => {
+ test("sends tool_name and user_id with the action's case intact", async () => {
+ const seen: unknown[] = [];
+ stub(
+ protoOf((c) => c.tools),
+ "authorize",
+ (params) => {
+ seen.push(params);
+ return Promise.resolve({
+ id: "au_1",
+ url: "https://consent.example/x",
+ });
+ },
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ const out = await broker.authorize({
+ account,
+ toolkit: "Github",
+ returnUrl: "https://app.example/settings",
+ action: "Github.CreateIssue",
+ });
+ expect(seen).toHaveLength(1);
+ const params = seen[0] as Record;
+ expect(params.tool_name).toBe("Github.CreateIssue");
+ expect(params.user_id).toBe("vendor-u1");
+ // The mint's own `ar_…` id rides out beside the url: spec D24 binds it to the callback's auth_id.
+ expect(out).toEqual({
+ redirectUrl: "https://consent.example/x",
+ authorizationId: "au_1",
+ });
+ });
+
+ test("returns the mint's id as authorizationId, untouched", async () => {
+ stub(
+ protoOf((c) => c.tools),
+ "authorize",
+ () =>
+ Promise.resolve({ id: "ar_abc123", url: "https://consent.example/y" }),
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ const out = await broker.authorize({
+ account,
+ toolkit: "Github",
+ returnUrl: "https://app.example/settings",
+ action: "Github.CreateIssue",
+ });
+ expect(out.authorizationId).toBe("ar_abc123");
+ });
+
+ test("a mint that answers no id is not a success, so no null is ever minted", async () => {
+ stub(
+ protoOf((c) => c.tools),
+ "authorize",
+ () => Promise.resolve({ url: "https://consent.example/y" }),
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ await expect(
+ broker.authorize({
+ account,
+ toolkit: "Github",
+ returnUrl: "https://app.example/settings",
+ action: "Github.CreateIssue",
+ }),
+ ).rejects.toThrow("no id");
+ });
+
+ test("with no action it refuses without dialling", async () => {
+ const seen: unknown[] = [];
+ stub(
+ protoOf((c) => c.tools),
+ "authorize",
+ (params) => {
+ seen.push(params);
+ return Promise.resolve({});
+ },
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ await expect(
+ broker.authorize({
+ account,
+ toolkit: "Github",
+ returnUrl: "https://app.example/settings",
+ }),
+ ).rejects.toBeInstanceOf(BrokerRefusalError);
+ expect(seen).toHaveLength(0);
+ });
+
+ test("the outgoing authorize params contain no next_uri key at all", async () => {
+ const seen: unknown[] = [];
+ stub(
+ protoOf((c) => c.tools),
+ "authorize",
+ (params) => {
+ seen.push(params);
+ return Promise.resolve({
+ id: "au_1",
+ url: "https://consent.example/x",
+ });
+ },
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ await broker.authorize({
+ account,
+ toolkit: "Github",
+ returnUrl: "https://app.example/settings",
+ action: "Github.CreateIssue",
+ });
+ const params = seen[0] as object;
+ expect(Object.keys(params)).not.toContain("next_uri");
+ expect("next_uri" in params).toBe(false);
+ expect(JSON.stringify(params)).not.toContain("app.example");
+ });
+});
+
+describe("arcade adapter confirmReturn", () => {
+ const confirmUser = (impl: (...args: unknown[]) => unknown) =>
+ stub(
+ protoOf((c) => c.auth),
+ "confirmUser",
+ impl,
+ );
+
+ test("returns the authorizationId, sending the handle as flow_id", async () => {
+ const seen: unknown[] = [];
+ confirmUser((params) => {
+ seen.push(params);
+ return Promise.resolve({ auth_id: "au_9" });
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const out = await broker.authorizations?.confirmReturn({
+ handle: "flow-abc",
+ vendorUserId: "vendor-u1",
+ });
+ expect(out).toEqual({ authorizationId: "au_9" });
+ expect(seen[0]).toEqual({ flow_id: "flow-abc", user_id: "vendor-u1" });
+ });
+
+ test("reports user_mismatch as { mismatch: true }, not a throw", async () => {
+ confirmUser(() =>
+ Promise.reject(
+ new APIError(
+ 400,
+ { name: "user_mismatch", message: "wrong user" },
+ "400",
+ new Headers(),
+ ),
+ ),
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ const out = await broker.authorizations?.confirmReturn({
+ handle: "flow-abc",
+ vendorUserId: "someone-else",
+ });
+ expect(out).toEqual({ mismatch: true });
+ });
+
+ test("any other failure still throws", async () => {
+ confirmUser(() =>
+ Promise.reject(
+ new APIError(500, { name: "boom", message: "x" }, "500", new Headers()),
+ ),
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ await expect(
+ broker.authorizations?.confirmReturn({
+ handle: "flow-abc",
+ vendorUserId: "vendor-u1",
+ }),
+ ).rejects.toMatchObject({ status: 500 });
+ });
+});
diff --git a/server/tests/arcade-adapter-connections.test.ts b/server/tests/arcade-adapter-connections.test.ts
new file mode 100644
index 000000000..0de1b587b
--- /dev/null
+++ b/server/tests/arcade-adapter-connections.test.ts
@@ -0,0 +1,792 @@
+import { afterEach, beforeEach, describe, expect, test } from "bun:test";
+import Arcade from "@arcadeai/arcadejs";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import { brokerSentence } from "../src/plugins/broker";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+
+/** Prototype stubs BEFORE construction; `new Arcade({ apiKey })` opens no socket. */
+type Methods = Record unknown>;
+const connectionsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).admin.userConnections,
+ ) as Methods;
+
+const toolsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).tools,
+ ) as Methods;
+
+const authProvidersProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).admin.authProviders,
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+ on: Methods = connectionsProto(),
+) {
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+ useArcadeClient(null);
+});
+
+const account: AccountRef = {
+ holder: "person",
+ userId: "u1",
+ vendorUserId: "vendor-u1",
+};
+
+/**
+ * One user-connection row, whose `provider_id` is a provider INSTANCE id and NEVER a provider type.
+ *
+ * That is the live shape, and it is the whole subject of this file: on a tool listing
+ * `provider_id` is the TYPE (`google`), on a connection row it is the operator's CONFIGURATION of
+ * that type (`google-openbot`, `arcade-google`, `cpk-poc`). A fixture spelling both sides alike —
+ * which every fixture here once did — makes the two meanings coincide and hides a comparison that
+ * matches nothing on any real deployment.
+ */
+const row = (n: number, instance: string, info?: unknown) => ({
+ id: `at_${n}`,
+ connection_id: `ac_${n}`,
+ provider_id: instance,
+ provider_user_info: info,
+});
+
+/**
+ * What the toolkit's listing says its auth provider TYPE is, keyed by the toolkit asked about. A
+ * toolkit absent from the map is answered by a listing that names no provider, i.e. UNRESOLVED.
+ * Counts the listing reads so a test can pin that a call resolves once.
+ */
+function providers(byToolkit: Record) {
+ const asked: unknown[] = [];
+ stub(
+ "list",
+ (query) => {
+ asked.push(query);
+ const toolkit = (query as { toolkit: string }).toolkit;
+ const provider = byToolkit[toolkit];
+ return Promise.resolve({
+ items: [
+ {
+ qualified_name: `${toolkit}.A`,
+ requirements: {
+ authorization: {
+ id: "x",
+ ...(provider ? { provider_id: provider } : {}),
+ },
+ },
+ },
+ ],
+ });
+ },
+ toolsProto(),
+ );
+ return asked;
+}
+
+/**
+ * The project's configured auth providers, verbatim in shape from the live project: `id` is the
+ * INSTANCE and `provider_id` is the TYPE. Two instances share the type `google` on purpose — an
+ * operator's own and one of Arcade's defaults — because a person may hold a connection under
+ * either and reducing the type to one instance would drop one of them.
+ */
+const CONFIGS = [
+ { id: "cpk-poc", provider_id: "github", type: "oauth2", status: "active" },
+ {
+ id: "google-openbot",
+ provider_id: "google",
+ type: "oauth2",
+ status: "active",
+ },
+ {
+ id: "arcade-google",
+ provider_id: "google",
+ type: "oauth2",
+ status: "active",
+ },
+];
+
+/**
+ * The configuration listing, PAGED BY THE QUERY IT IS HANDED — the sibling of {@link listing}, and
+ * scoped for the same reason.
+ *
+ * `admin.authProviders.list(options)` takes a bare `RequestOptions`, so its paging lives in a
+ * NESTED `{ query: { limit, offset } }` and nowhere else: a flat `{ limit, offset }` is not an error
+ * the SDK reports, it is a request carrying no query at all, answered with whatever page the vendor
+ * defaults to. A stub that served every config to every call made that shape — and the size and
+ * position of the page the adapter asks for — unobservable: asking for nothing, asking flat, or
+ * asking for the wrong offset all passed every test in this file. So a page is served only to a
+ * `query` naming a numeric `limit` and `offset`, sliced exactly as asked, and any other call gets a
+ * real empty page whose `total_count` still counts every config — so the adapter's own
+ * incompleteness cross-check fails loudly rather than answering a silent "not connected".
+ *
+ * Returns the options it was asked, so a test can assert the shape by name as well as by effect,
+ * and pin that a call reads the configurations once.
+ */
+type ConfigsOptions = { query?: { limit?: unknown; offset?: unknown } };
+function configs(items: unknown[] = CONFIGS) {
+ const asked: ConfigsOptions[] = [];
+ stub(
+ "list",
+ (options) => {
+ const o = (options ?? {}) as ConfigsOptions;
+ asked.push(o);
+ const { limit, offset } = o.query ?? {};
+ if (typeof limit !== "number" || typeof offset !== "number") {
+ return Promise.resolve({ items: [], total_count: items.length });
+ }
+ return Promise.resolve({
+ items: items.slice(offset, offset + limit),
+ offset,
+ limit,
+ total_count: items.length,
+ });
+ },
+ authProvidersProto(),
+ );
+ return asked;
+}
+
+/**
+ * A PAGE, NOT A BARE GENERATOR. `connectionsFor` pages the connection listing itself rather than
+ * leaning on the SDK's auto-pagination, because `OffsetPage` stops after page one whenever the
+ * engine omits `total_count` — see `arcade-pagination-engine.test.ts`, which proves that against a
+ * real HTTP engine. So the stub answers what the real `list` resolves to: an object with `items`,
+ * honouring the `limit`/`offset` the adapter sends.
+ *
+ * AND SCOPED TO THE IDENTITY THE QUERY NAMES, which is the other half of what the real listing
+ * does and what this stub once left out. `admin.userConnections.list` answers the connections of
+ * ONE `user_id`; a stub that served its rows to every query made the identity the adapter sends
+ * unobservable, so sending `account.userId` — the LOCAL id — or sending no `user_id` at all passed
+ * every test in this file. Scoping the read to `account.vendorUserId` is the whole subject of
+ * commit `28b13e4a`, a live defect in which one person's Disconnect could read, and spend DELETEs
+ * against, another person's connections. So: rows are served only to a query naming `heldBy`
+ * (the vendor identity by default), and any other query — wrong id, or none — gets a real empty
+ * page, exactly as the vendor would answer for an identity holding nothing.
+ *
+ * Returns the queries it was asked, so a test can assert the identity by name as well as by effect.
+ */
+type ListingQuery = { user_id?: unknown; limit?: number; offset?: number };
+function listing(rows: unknown[], heldBy: string = account.vendorUserId) {
+ const asked: ListingQuery[] = [];
+ stub("list", (query) => {
+ const q = (query ?? {}) as ListingQuery;
+ asked.push(q);
+ const mine = q.user_id === heldBy ? rows : [];
+ const offset = q.offset ?? 0;
+ const limit = q.limit ?? mine.length;
+ return Promise.resolve({
+ items: mine.slice(offset, offset + limit),
+ offset,
+ limit,
+ total_count: mine.length,
+ });
+ });
+ return asked;
+}
+
+/** The TYPE each toolkit's listing names. Nothing here is an instance id. */
+const TOOLKIT_TYPES = {
+ Github: "github",
+ GitHub: "github",
+ Google: "google",
+ Gmail: "google",
+};
+
+describe("arcade adapter connection lifecycle", () => {
+ beforeEach(() => {
+ providers(TOOLKIT_TYPES);
+ configs();
+ });
+
+ test("isConnected is true on a non-empty list and false on an empty one", async () => {
+ const rows: unknown[] = [row(1, "cpk-poc")];
+ listing(rows);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(true);
+ rows.length = 0;
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(
+ false,
+ );
+ });
+
+ test("isConnected ignores another app's connection", async () => {
+ listing([row(1, "google-openbot")]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(
+ false,
+ );
+ });
+
+ /*
+ * THE IDENTITY THE LISTING IS READ FOR, ASSERTED BY NAME. `AccountRef` carries two ids and they
+ * are not interchangeable: `userId` is this deployment's own, `vendorUserId` is the one Arcade
+ * files a connection under. Reading the listing for the wrong one is what commit `28b13e4a`
+ * fixed in the source, and sending no `user_id` at all is worse still — `admin.userConnections`
+ * then answers EVERY account's connections, which `revoke` would attribute to this one and
+ * spend DELETEs against. Both are spelled out here because an effect-only assertion cannot tell
+ * the difference between "scoped to the right id" and "scoped to a wrong id that happens to
+ * hold these rows".
+ */
+ test("the connection listing is read for the VENDOR identity, on every page and every member", async () => {
+ const asked = listing([row(1, "cpk-poc", { name: "Ada" })]);
+ stub("delete", () => Promise.resolve());
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Github";
+ expect(await broker.isConnected({ account, toolkit })).toBe(true);
+ expect(await broker.accountName({ account, toolkit })).toBe("Ada");
+ expect(await broker.revoke({ account, toolkit })).toBe(true);
+ expect(asked.length).toBeGreaterThan(0);
+ for (const query of asked) {
+ expect(query.user_id).toBe(account.vendorUserId);
+ expect(query.user_id).not.toBe(account.userId);
+ }
+ });
+
+ test("connections held by ANOTHER vendor identity are neither read nor deleted", async () => {
+ listing([row(1, "cpk-poc", { name: "Ada" })], "vendor-somebody-else");
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Github";
+ expect(await broker.isConnected({ account, toolkit })).toBe(false);
+ expect(await broker.accountName({ account, toolkit })).toBeNull();
+ expect(await broker.revoke({ account, toolkit })).toBe(false);
+ expect(deleted).toEqual([]);
+ });
+
+ test("delete is passed the ac_ connection_id, never the at_ id", async () => {
+ listing([row(7, "cpk-poc")]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.revoke({ account, toolkit: "Github" })).toBe(true);
+ expect(deleted).toEqual(["ac_7"]);
+ expect(String(deleted[0]).startsWith("at_")).toBe(false);
+ });
+
+ test("revokeAccount deletes by the id it is handed", async () => {
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ await broker.revokeAccount("ac_9");
+ expect(deleted).toEqual(["ac_9"]);
+ });
+
+ test("revoke answers false on nothing-found and deletes nothing", async () => {
+ listing([row(1, "google-openbot")]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.revoke({ account, toolkit: "Github" })).toBe(false);
+ expect(deleted).toEqual([]);
+ });
+
+ test("revoke THROWS when one of several deletes fails, after trying them all", async () => {
+ listing([row(1, "cpk-poc"), row(2, "cpk-poc"), row(3, "cpk-poc")]);
+ const tried: unknown[] = [];
+ stub("delete", (id) => {
+ tried.push(id);
+ return id === "ac_2"
+ ? Promise.reject(new Error("500"))
+ : Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ let outcome: unknown = "returned";
+ try {
+ outcome = await broker.revoke({ account, toolkit: "Github" });
+ } catch (error) {
+ outcome = error;
+ }
+ expect(outcome).toBeInstanceOf(Error);
+ expect((outcome as Error).message).toMatch(/1 of 3/);
+ expect(tried).toEqual(["ac_1", "ac_2", "ac_3"]);
+ });
+
+ test("accountName never answers another app's name", async () => {
+ listing([
+ row(1, "cpk-poc", {}),
+ row(2, "google-openbot", { name: "Grace" }),
+ row(3, "cpk-poc", { name: "Ada" }),
+ ]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.accountName({ account, toolkit: "GitHub" })).toBe(
+ "Ada",
+ );
+ expect(await broker.accountName({ account, toolkit: "Google" })).toBe(
+ "Grace",
+ );
+ });
+
+ test("accountName is null, not the second app's name, when the first carries none", async () => {
+ listing([
+ row(1, "cpk-poc", {}),
+ row(2, "google-openbot", { name: "Grace" }),
+ ]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.accountName({ account, toolkit: "Github" })).toBeNull();
+ });
+
+ /*
+ * THE DEFECT THIS FILE EXISTS FOR, AT ITS THREE MEMBERS. `Gmail` names the TYPE `google`; the
+ * connection is held under the INSTANCE `google-openbot`. A comparison of the two raw strings
+ * finds nothing — and `revoke` then answers false WITHOUT SPENDING A DELETE, so the vendor
+ * connection outlives the Disconnect that was supposed to end it.
+ */
+ test("a Gmail connection under the custom instance `google-openbot` is FOUND by all three members", async () => {
+ listing([row(4, "google-openbot", { name: "Grace" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ expect(await broker.isConnected({ account, toolkit })).toBe(true);
+ expect(await broker.accountName({ account, toolkit })).toBe("Grace");
+ expect(await broker.revoke({ account, toolkit })).toBe(true);
+ // The `ac_…` connection_id, never the `at_…` row id.
+ expect(deleted).toEqual(["ac_4"]);
+ });
+
+ test("a Gmail connection under Arcade's DEFAULT instance `arcade-google` is found too", async () => {
+ listing([row(5, "arcade-google", { name: "Ada" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ expect(await broker.isConnected({ account, toolkit })).toBe(true);
+ expect(await broker.accountName({ account, toolkit })).toBe("Ada");
+ expect(await broker.revoke({ account, toolkit })).toBe(true);
+ expect(deleted).toEqual(["ac_5"]);
+ });
+
+ test("BOTH instances of one type are matched; neither is dropped for the other", async () => {
+ listing([row(4, "google-openbot"), row(5, "arcade-google")]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.revoke({ account, toolkit: "Gmail" })).toBe(true);
+ expect(deleted.sort()).toEqual(["ac_4", "ac_5"]);
+ });
+
+ test("an UNRELATED instance (`cpk-poc`, type github) is not a Gmail connection", async () => {
+ listing([row(1, "cpk-poc", { name: "Ada" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ expect(await broker.isConnected({ account, toolkit })).toBe(false);
+ expect(await broker.accountName({ account, toolkit })).toBeNull();
+ expect(await broker.revoke({ account, toolkit })).toBe(false);
+ expect(deleted).toEqual([]);
+ });
+
+ test("the TYPE itself is not an instance id, so a row spelled `google` is not matched", async () => {
+ // Nothing anywhere falls back to comparing the raw type against a row.
+ listing([row(6, "google", { name: "Grace" })]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Gmail" })).toBe(false);
+ });
+
+ test("an instance the configuration list does not name is fail-closed, not deleted", async () => {
+ // A provider configuration deleted while a connection outlived it.
+ listing([row(8, "retired-google", { name: "Grace" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.revoke({ account, toolkit: "Gmail" })).toBe(false);
+ expect(deleted).toEqual([]);
+ });
+
+ test("both the type and the instance are compared case-insensitively", async () => {
+ providers({ Gmail: "GOOGLE" });
+ configs([{ id: "Google-OpenBot", provider_id: "Google" }]);
+ listing([row(4, "google-openbot", { name: "Grace" })]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Gmail" })).toBe(true);
+ });
+
+ test("a different provider on the same identity is still excluded", async () => {
+ listing([row(1, "cpk-poc", { name: "Ada" }), row(2, "google-openbot", {})]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.accountName({ account, toolkit: "Gmail" })).toBeNull();
+ expect(await broker.revoke({ account, toolkit: "Gmail" })).toBe(true);
+ expect(deleted).toEqual(["ac_2"]);
+ });
+
+ /**
+ * AN UNATTRIBUTABLE TOOLKIT REFUSES OUT OF ALL THREE LIFECYCLE MEMBERS, AND NEVER FALLS BACK TO
+ * ITS NAME. Both halves matter and they used to be answered the same wrong way.
+ *
+ * The fallback is still refused: each fixture below serves a row whose instance id IS literally
+ * the toolkit name folded down, so a fallback would find it and delete it — attributing somebody
+ * else's connection to this app on a guess.
+ *
+ * And answering NO ROWS is refused too, which is this pair's reason for existing. `revoke` then
+ * said `false` — "there was nothing to withdraw" — without spending a single DELETE, while
+ * `store.ts`'s disconnect path deletes the local `brokered_connections` row on that answer
+ * regardless: a live grant at the vendor that no row this deployment holds any longer names.
+ * `isConnected` and `accountName` said not-connected for the same person, so Connect offered a
+ * second authorization beside the live one. The listing was not read; refusing is the only
+ * honest answer, and `broker.ts`'s `revoke` contract requires it.
+ */
+ test("a toolkit whose listing names NO provider refuses rather than answering no rows", async () => {
+ providers({});
+ listing([row(1, "gmail", { name: "Grace" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ const refusal = /names no auth provider at all/;
+ await expect(broker.isConnected({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ await expect(broker.accountName({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ await expect(broker.revoke({ account, toolkit })).rejects.toThrow(refusal);
+ expect(deleted).toEqual([]);
+ });
+
+ test("a toolkit whose listing names TWO providers refuses, naming both", async () => {
+ // Two actions of one toolkit under two different provider types: the toolkit disagrees with
+ // itself, so which of this account's connections are its own cannot be read off the listing.
+ stub(
+ "list",
+ (query) => {
+ const toolkit = (query as { toolkit: string }).toolkit;
+ return Promise.resolve({
+ items: ["microsoft", "google"].map((provider, n) => ({
+ qualified_name: `${toolkit}.A${n}`,
+ requirements: { authorization: { id: "x", provider_id: provider } },
+ })),
+ });
+ },
+ toolsProto(),
+ );
+ listing([row(1, "gmail", { name: "Grace" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ // Sorted, so the sentence does not change with the order the listing happened to arrive in.
+ const refusal = /names 2 different auth providers \(google, microsoft\)/;
+ await expect(broker.isConnected({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ await expect(broker.accountName({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ await expect(broker.revoke({ account, toolkit })).rejects.toThrow(refusal);
+ expect(deleted).toEqual([]);
+ });
+
+ test("the unattributable refusal is an AUTHORED one, so the route shows its sentence", async () => {
+ // A `BrokerRefusalError` and not a bare `Error`: `brokerSentence` is what the Disconnect and
+ // Connect routes read, and a bare error would have them say "the vendor said nothing about
+ // why, check the key" about a sentence this deployment wrote itself.
+ providers({});
+ listing([row(1, "google-openbot")]);
+ const { broker } = createArcadeClient("never-dialled");
+ const error = await broker
+ .revoke({ account, toolkit: "Gmail" })
+ .then(() => null)
+ .catch((thrown: unknown) => thrown);
+ expect(brokerSentence(error)).toMatch(/names no auth provider at all/);
+ });
+
+ test("a malformed listing THROWS out of the lifecycle members rather than answering no rows", async () => {
+ stub("list", () => Promise.resolve({ items: "nope" }), toolsProto());
+ listing([row(1, "google-openbot")]);
+ const { broker } = createArcadeClient("never-dialled");
+ await expect(broker.revoke({ account, toolkit: "Gmail" })).rejects.toThrow(
+ /action list/,
+ );
+ });
+
+ test("a malformed auth-provider listing THROWS rather than answering no rows", async () => {
+ stub(
+ "list",
+ () => Promise.resolve({ items: "nope" }),
+ authProvidersProto(),
+ );
+ listing([row(1, "google-openbot")]);
+ const { broker } = createArcadeClient("never-dialled");
+ await expect(broker.revoke({ account, toolkit: "Gmail" })).rejects.toThrow(
+ /auth-provider list/,
+ );
+ });
+
+ /**
+ * AN AUTHORIZATION BLOCK THIS DEPLOYMENT CANNOT READ IS NOT AN ACTION THAT NAMES NO PROVIDER.
+ *
+ * `authProvidersNamedBy` once skipped it in silence, so a `Gmail` listing of one readable
+ * `google` action beside one whose authorization arrived unreadable resolved to `google`, and
+ * all three members answered — `revoke` spending DELETEs — off a listing that was not read whole.
+ * The unreadable action may name another provider; one id beside it is not an attribution.
+ */
+ const beside = (unreadable: Record) =>
+ stub(
+ "list",
+ (query) => {
+ const toolkit = (query as { toolkit: string }).toolkit;
+ return Promise.resolve({
+ items: [
+ {
+ qualified_name: `${toolkit}.Readable`,
+ requirements: {
+ authorization: { id: "x", provider_id: "google" },
+ },
+ },
+ { qualified_name: `${toolkit}.Unreadable`, ...unreadable },
+ ],
+ });
+ },
+ toolsProto(),
+ );
+
+ test.each([
+ [
+ "an authorization that is a string",
+ { requirements: { authorization: "oauth2" } },
+ /Arcade sent a string where Gmail's action Gmail\.Unreadable carries its authorization requirement/,
+ ],
+ [
+ "requirements that are a list",
+ { requirements: ["oauth2"] },
+ /Arcade sent a list where Gmail's action Gmail\.Unreadable carries its requirements/,
+ ],
+ [
+ "a provider_id that is a number",
+ { requirements: { authorization: { id: "x", provider_id: 42 } } },
+ /Arcade sent a number where Gmail's action Gmail\.Unreadable carries its auth provider id/,
+ ],
+ ])(
+ "an action carrying %s REFUSES, rather than resolving off the readable one",
+ async (_, unreadable, refusal) => {
+ beside(unreadable);
+ listing([row(1, "google-openbot", { name: "Grace" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Gmail";
+ await expect(broker.isConnected({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ await expect(broker.accountName({ account, toolkit })).rejects.toThrow(
+ refusal,
+ );
+ const error = await broker
+ .revoke({ account, toolkit })
+ .then(() => null)
+ .catch((thrown: unknown) => thrown);
+ // AUTHORED, so the Disconnect route shows this sentence rather than vendor-outage advice.
+ expect(brokerSentence(error)).toMatch(refusal);
+ expect(deleted).toEqual([]);
+ },
+ );
+
+ test("an action with NO authorization block beside a readable one still resolves — absence is not unreadability", async () => {
+ beside({ requirements: { authorization: null } });
+ listing([row(1, "google-openbot", { name: "Grace" })]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Gmail" })).toBe(true);
+ });
+
+ test("each call resolves the provider and its configurations once, however many rows", async () => {
+ const asked = providers({ Gmail: "google" });
+ const read = configs();
+ listing([
+ row(1, "google-openbot"),
+ row(2, "google-openbot"),
+ row(3, "arcade-google"),
+ ]);
+ stub("delete", () => Promise.resolve());
+ const { broker } = createArcadeClient("never-dialled");
+ await broker.revoke({ account, toolkit: "Gmail" });
+ expect(asked).toHaveLength(1);
+ expect(read).toHaveLength(1);
+ });
+
+ /*
+ * THE CONFIGURATION LISTING'S PAGING, ASSERTED BY NAME AND BY EFFECT. The instance a person is
+ * held under sits on PAGE TWO, behind a full page of other configurations, so an adapter that
+ * reads one page, asks flat, or asks for the wrong offset cannot find it; and the options are
+ * pinned verbatim, because `RequestOptions` drops anything outside `query` without a word.
+ */
+ test("the auth-provider listing is paged in the nested query shape, to the page holding the instance", async () => {
+ const filler = Array.from({ length: 100 }, (_, n) => ({
+ id: `filler-${n}`,
+ provider_id: "github",
+ }));
+ const read = configs([
+ ...filler,
+ { id: "google-openbot", provider_id: "google" },
+ ]);
+ listing([row(1, "google-openbot", { name: "Grace" })]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Gmail" })).toBe(true);
+ expect(read).toEqual([
+ { query: { limit: 100, offset: 0 } },
+ { query: { limit: 100, offset: 100 } },
+ ]);
+ });
+});
+
+/**
+ * The same row, carrying the vendor's OWN answer to "is this connection still usable".
+ *
+ * Separate from {@link row} rather than a fourth parameter on it, because the point is that the
+ * field is OPTIONAL on the wire: every fixture above omits it on purpose, and omission must keep
+ * meaning "the vendor said nothing". A `null` status omits the key entirely, which is the shape an
+ * engine that does not send the field answers with.
+ */
+const statusRow = (
+ n: number,
+ instance: string,
+ status: string | null,
+ info?: unknown,
+) => ({
+ ...row(n, instance, info),
+ ...(status === null ? {} : { connection_status: status }),
+});
+
+/**
+ * THE ROW'S EXISTENCE IS NOT THE ANSWER, AND THE THREE MEMBERS DO NOT AGREE ABOUT WHAT IS.
+ *
+ * `connection_status` is Arcade saying whether a connection still works. The positive reads —
+ * `isConnected`, `accountName` — must SKIP a row it reports as expired, revoked, pending or failed,
+ * because a screen saying "Connected, as Grace" off such a row is wrong in the one place somebody
+ * looks to check, and nothing contradicts it until a tool call fails far away from here.
+ *
+ * `revoke` must STILL DELETE that row, which is the half a fixture set of live-only rows cannot
+ * tell apart from the other. A dead row is a row the VENDOR still holds; skipping it would have
+ * `revoke` answer `false` — "there was nothing to withdraw" — while `store.ts`'s disconnect path
+ * deletes the local `brokered_connections` row on that answer regardless, leaving a grant at the
+ * vendor that nothing this deployment holds any longer names. That is the exact signature of the
+ * live defect this seam has already shipped once.
+ *
+ * Every fixture here is under `cpk-poc`/`google-openbot`, INSTANCE ids this project's
+ * configuration names, so nothing below can pass or fail for the instance-vs-type reason the rest
+ * of this file is about: the only variable is the status.
+ */
+describe("a connection the vendor reports as dead", () => {
+ beforeEach(() => {
+ providers(TOOLKIT_TYPES);
+ configs();
+ });
+
+ test("is skipped by isConnected and accountName, and is STILL DELETED by revoke", async () => {
+ listing([statusRow(1, "cpk-poc", "expired", { name: "Ada" })]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Github";
+ expect(await broker.isConnected({ account, toolkit })).toBe(false);
+ expect(await broker.accountName({ account, toolkit })).toBeNull();
+ // Not `false`-with-no-delete: the row is the vendor's, and Disconnect must spend the call.
+ expect(await broker.revoke({ account, toolkit })).toBe(true);
+ expect(deleted).toEqual(["ac_1"]);
+ });
+
+ test("beside a live row, the LIVE one answers and revoke deletes both", async () => {
+ listing([
+ statusRow(1, "cpk-poc", "expired", { name: "Ada" }),
+ statusRow(2, "cpk-poc", "active", { name: "Grace" }),
+ ]);
+ const deleted: unknown[] = [];
+ stub("delete", (id) => {
+ deleted.push(id);
+ return Promise.resolve();
+ });
+ const { broker } = createArcadeClient("never-dialled");
+ const toolkit = "Github";
+ expect(await broker.isConnected({ account, toolkit })).toBe(true);
+ // The dead row is FIRST, so a member that stopped at the first row would say "Ada".
+ expect(await broker.accountName({ account, toolkit })).toBe("Grace");
+ expect(await broker.revoke({ account, toolkit })).toBe(true);
+ expect([...deleted].sort()).toEqual(["ac_1", "ac_2"]);
+ });
+
+ /*
+ * K2 SETTLED WHAT "BLANK" MEANS, ONCE, FOR EVERY VENDOR DECISION FIELD: a status the vendor did
+ * not send (or sent as `null`) is SILENCE and the row stands; a status sent BLANK is present and
+ * carries nothing, which is UNREADABLE — the judgement `authProvidersNamedBy` makes of a blank
+ * `provider_id` — and an unreadable status is not "live". This row once asserted the opposite for
+ * blank, which made the two readers disagree about the same wire value.
+ */
+ test("a status the vendor did not send is SILENCE and still reads as live; a blank one does not", async () => {
+ listing([
+ statusRow(1, "cpk-poc", null, { name: "Ada" }),
+ statusRow(2, "google-openbot", "", { name: "Grace" }),
+ ]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(true);
+ expect(await broker.accountName({ account, toolkit: "Github" })).toBe(
+ "Ada",
+ );
+ expect(await broker.isConnected({ account, toolkit: "Gmail" })).toBe(false);
+ expect(await broker.accountName({ account, toolkit: "Gmail" })).toBeNull();
+ });
+
+ test("the status is folded and trimmed, so ` ACTIVE ` is live and `Expired` is not", async () => {
+ listing([statusRow(1, "cpk-poc", " ACTIVE ", { name: "Ada" })]);
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(true);
+ listing([statusRow(2, "cpk-poc", "Expired", { name: "Ada" })]);
+ expect(await broker.isConnected({ account, toolkit: "Github" })).toBe(
+ false,
+ );
+ });
+});
diff --git a/server/tests/arcade-adapter-errors.test.ts b/server/tests/arcade-adapter-errors.test.ts
new file mode 100644
index 000000000..4c937fac6
--- /dev/null
+++ b/server/tests/arcade-adapter-errors.test.ts
@@ -0,0 +1,67 @@
+import { describe, expect, test } from "bun:test";
+import { APIConnectionError, APIError } from "@arcadeai/arcadejs";
+import { normalizeArcadeError } from "../src/plugins/arcade-adapter";
+
+// The SDK's real shape: constructor(status, error, message, headers); own fields status,
+// headers, error. Nothing on it names the tool.
+const refusal = () =>
+ new APIError(
+ 403,
+ { name: "tool_authorization_required", message: "Authorize Gmail first" },
+ undefined,
+ new Headers(),
+ );
+
+describe("normalizeArcadeError", () => {
+ test("a realistically-shaped 403 normalizes", () => {
+ expect(normalizeArcadeError(refusal())).toEqual({
+ status: 403,
+ name: "tool_authorization_required",
+ message: "Authorize Gmail first",
+ });
+ });
+
+ test("the same error wrapped in cause still normalizes", () => {
+ const wrapped = new Error("outer", {
+ cause: new Error("middle", { cause: refusal() }),
+ });
+ expect(normalizeArcadeError(wrapped)).toEqual({
+ status: 403,
+ name: "tool_authorization_required",
+ message: "Authorize Gmail first",
+ });
+ });
+
+ test("a plain Error answers null", () => {
+ expect(normalizeArcadeError(new Error("boom"))).toBeNull();
+ });
+
+ test("non-errors answer null", () => {
+ expect(normalizeArcadeError(undefined)).toBeNull();
+ expect(normalizeArcadeError("403")).toBeNull();
+ });
+
+ test("a cyclic cause chain terminates with null", () => {
+ const a: Error & { cause?: unknown } = new Error("a");
+ a.cause = a;
+ expect(normalizeArcadeError(a)).toBeNull();
+ });
+
+ test("an error with no body leaves name and message undefined", () => {
+ expect(
+ normalizeArcadeError(
+ new APIError(500, undefined, undefined, new Headers()),
+ ),
+ ).toEqual({
+ status: 500,
+ name: undefined,
+ message: undefined,
+ });
+ });
+
+ test("a connection failure keeps status undefined", () => {
+ expect(
+ normalizeArcadeError(new APIConnectionError({ message: "down" }))?.status,
+ ).toBeUndefined();
+ });
+});
diff --git a/server/tests/arcade-adapter-status.test.ts b/server/tests/arcade-adapter-status.test.ts
new file mode 100644
index 000000000..1a44abf19
--- /dev/null
+++ b/server/tests/arcade-adapter-status.test.ts
@@ -0,0 +1,328 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import Arcade from "@arcadeai/arcadejs";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+
+/** Prototype stubs BEFORE construction; `new Arcade({ apiKey })` opens no socket. */
+type Methods = Record unknown>;
+const protoOf = (pick: (c: Arcade) => unknown): Methods =>
+ Object.getPrototypeOf(
+ pick(new Arcade({ apiKey: "never-dialled" })),
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ pick: (c: Arcade) => unknown,
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+) {
+ const on = protoOf(pick);
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+ useArcadeClient(null);
+});
+
+const account: AccountRef = {
+ holder: "person",
+ userId: "u1",
+ vendorUserId: "vendor-u1",
+};
+
+/** The listing calls, so a test can assert what went out with them. */
+const listCalls: unknown[] = [];
+function listing(items: unknown[]) {
+ listCalls.length = 0;
+ stub(
+ (c) => c.tools,
+ "list",
+ (query) => {
+ listCalls.push(query);
+ return Promise.resolve({ items });
+ },
+ );
+}
+
+const tool = (name: string, requirements: unknown) => ({
+ fully_qualified_name: `${name}@1.0.0`,
+ qualified_name: name,
+ name: name.split(".")[1],
+ toolkit: { name: "Github" },
+ requirements,
+});
+
+describe("grantsFor maps a toolkit listing to this deployment's vocabulary", () => {
+ test("sends the user_id it was given and maps all four states", async () => {
+ listing([
+ tool("Github.SetStarred", {
+ met: true,
+ authorization: {
+ provider_id: "github",
+ status: "active",
+ token_status: "completed",
+ oauth2: { scopes: ["repo", "user"] },
+ },
+ secrets: [],
+ }),
+ tool("Github.ListStargazers", {
+ met: false,
+ authorization: {
+ provider_id: "github",
+ status: "inactive",
+ token_status: "not_started",
+ oauth2: { scopes: ["repo"] },
+ },
+ secrets: [],
+ }),
+ tool("Github.CreateIssue", {
+ met: false,
+ authorization: {
+ provider_id: "github",
+ status: "active",
+ token_status: "completed",
+ oauth2: { scopes: ["repo"] },
+ },
+ // The live listing: an operator sets this once for everybody and no amount
+ // of consenting moves it.
+ secrets: [
+ { key: "GITHUB_SERVER_URL", met: false, status_reason: "unset" },
+ ],
+ }),
+ // An authorization block in a shape this deployment cannot read. NOT a row with no block at
+ // all: that is the vendor saying the action needs none, and is `authorized` — see below.
+ tool("Github.CountStargazers", { met: false, authorization: "oauth2" }),
+ ]);
+
+ const { broker } = createArcadeClient("never-dialled");
+ const grants = await broker.grantsFor({ toolkit: "Github", account });
+
+ expect(listCalls).toHaveLength(1);
+ expect(listCalls[0]).toMatchObject({
+ toolkit: "Github",
+ user_id: "vendor-u1",
+ });
+
+ expect(grants).toEqual([
+ {
+ action: "Github.SetStarred",
+ state: "authorized",
+ scopes: ["repo", "user"],
+ authorization: "held",
+ },
+ {
+ action: "Github.ListStargazers",
+ state: "needs-consent",
+ scopes: ["repo"],
+ },
+ {
+ action: "Github.CreateIssue",
+ state: "needs-operator",
+ missing: ["GITHUB_SERVER_URL"],
+ },
+ {
+ action: "Github.CountStargazers",
+ state: "unknown",
+ reason: expect.any(String),
+ },
+ ]);
+ });
+
+ test("an unreadable authorization is unknown, never a pessimistic needs-consent", async () => {
+ listing([
+ // An authorization block with neither a status nor a token_status: the listing
+ // says neither "connected" nor "not connected".
+ tool("Github.WhoAmI", {
+ met: false,
+ authorization: { provider_id: "github", oauth2: { scopes: ["user"] } },
+ secrets: [],
+ }),
+ ]);
+
+ const { broker } = createArcadeClient("never-dialled");
+ const grants = await broker.grantsFor({ toolkit: "Github", account });
+
+ expect(grants).toHaveLength(1);
+ expect(grants[0]?.state).toBe("unknown");
+ expect(grants[0]?.state).not.toBe("needs-consent");
+ expect(grants[0]).toEqual({
+ action: "Github.WhoAmI",
+ state: "unknown",
+ reason: expect.any(String),
+ });
+ });
+});
+
+/**
+ * ONE READING OF A MISSING AUTHORIZATION BLOCK, ASKED OF BOTH QUESTIONS ON THE SAME ROW.
+ *
+ * The catalogue files an app whose actions publish no `requirements.authorization` as `no-auth`;
+ * the grants screen once read that same silence as "could not be read" and drew every action of
+ * such an app as `unknown` — the broker-outage state — forever, with nothing anybody could do about
+ * it. `Apollo` is the live shape (one deployment secret, no authorization block), here with its
+ * secret SET, which is the state that used to stick.
+ */
+describe("an action that publishes no authorization needs none, on every screen", () => {
+ const silent = [
+ tool("Apollo.EnrichOrganization", {
+ met: true,
+ secrets: [{ key: "APOLLO_API_KEY", met: true }],
+ }),
+ tool("Apollo.SearchPeople", null),
+ {
+ qualified_name: "Apollo.SearchOrganizations",
+ toolkit: { name: "Github" },
+ },
+ ];
+
+ test("the catalogue says no-auth and the grants screen says usable, never unknown", async () => {
+ listing(silent);
+ const { broker } = createArcadeClient("never-dialled");
+
+ const apps = await broker.listApps();
+ expect(apps.map((app) => app.connection)).toEqual([{ kind: "no-auth" }]);
+
+ expect(await broker.grantsFor({ toolkit: "Github", account })).toEqual([
+ {
+ action: "Apollo.EnrichOrganization",
+ state: "authorized",
+ scopes: [],
+ authorization: "not-required",
+ },
+ {
+ action: "Apollo.SearchPeople",
+ state: "authorized",
+ scopes: [],
+ authorization: "not-required",
+ },
+ {
+ action: "Apollo.SearchOrganizations",
+ state: "authorized",
+ scopes: [],
+ authorization: "not-required",
+ },
+ ]);
+ });
+
+ test("an UNSET secret on such an action is still the operator's, not authorized", async () => {
+ listing([
+ tool("Apollo.EnrichOrganization", {
+ met: false,
+ secrets: [{ key: "APOLLO_API_KEY", met: false }],
+ }),
+ ]);
+ const { broker } = createArcadeClient("never-dialled");
+
+ expect(await broker.grantsFor({ toolkit: "Apollo", account })).toEqual([
+ {
+ action: "Apollo.EnrichOrganization",
+ state: "needs-operator",
+ missing: ["APOLLO_API_KEY"],
+ },
+ ]);
+ });
+});
+
+/**
+ * THE LEAK TEST. `auth.status` answers with a LIVE provider access token and the person's full
+ * profile; the narrowing is a type, not a rule somebody has to remember.
+ */
+const TOKEN = "gho_16C7e42F292c6912E7710c838347Ae178B4a";
+const PROFILE = {
+ email: "ada@example.com",
+ name: "Ada Lovelace",
+ login: "ada",
+ id: 4711,
+};
+
+function statusAnswers(answer: unknown) {
+ stub(
+ (c) => c.auth,
+ "status",
+ () => Promise.resolve(answer),
+ );
+}
+
+describe("authorizations.status narrows and drops the rest", () => {
+ const raw = {
+ id: "auth_123",
+ status: "completed",
+ user_id: "vendor-u1",
+ scopes: ["repo", "user"],
+ provider_id: "github",
+ url: "https://cloud.arcade.dev/approve/abc123",
+ context: { token: TOKEN, user_info: PROFILE },
+ };
+
+ test("returns exactly the three fields", async () => {
+ statusAnswers(raw);
+ const { broker } = createArcadeClient("never-dialled");
+ const flow = broker.authorizations;
+ if (!flow) throw new Error("Arcade must declare an authorization flow.");
+
+ const narrowed = await flow.status("auth_123");
+ expect(Object.keys(narrowed).sort()).toEqual([
+ "scopes",
+ "status",
+ "userId",
+ ]);
+ expect(narrowed).toEqual({
+ status: "completed",
+ userId: "vendor-u1",
+ scopes: ["repo", "user"],
+ });
+ });
+
+ test("the token, the profile and the url do not survive the narrowing", async () => {
+ statusAnswers(raw);
+ const { broker } = createArcadeClient("never-dialled");
+ const flow = broker.authorizations;
+ if (!flow) throw new Error("Arcade must declare an authorization flow.");
+
+ const serialized = JSON.stringify(await flow.status("auth_123"));
+ expect(serialized).not.toContain(TOKEN);
+ expect(serialized).not.toContain("gho_");
+ expect(serialized).not.toContain(PROFILE.email);
+ expect(serialized).not.toContain(PROFILE.name);
+ expect(serialized).not.toContain(PROFILE.login);
+ expect(serialized).not.toContain("user_info");
+ expect(serialized).not.toContain("token");
+ // An authorization url is a bearer capability and is not one of the three fields.
+ expect(serialized).not.toContain(raw.url);
+ expect(serialized).not.toContain("provider_id");
+ });
+
+ test("a missing status and user_id answer a string and a null, not undefined", async () => {
+ statusAnswers({ context: { token: TOKEN, user_info: PROFILE } });
+ const { broker } = createArcadeClient("never-dialled");
+ const flow = broker.authorizations;
+ if (!flow) throw new Error("Arcade must declare an authorization flow.");
+
+ const narrowed = await flow.status("auth_123");
+ expect(narrowed.userId).toBeNull();
+ expect(typeof narrowed.status).toBe("string");
+ expect(narrowed.scopes).toEqual([]);
+ expect(JSON.stringify(narrowed)).not.toContain(TOKEN);
+ });
+
+ /**
+ * AN EMPTY STATUS IS NOT A STATUS AND AN EMPTY USER ID IS NOT AN ID. `??` falls through on null
+ * and undefined only, so a `textOf` answering `""` as text handed the return leg an empty status
+ * in place of `"unknown"` and an empty string where the identity comparison expects `null`.
+ */
+ test("an empty status and user_id read as absent, not as empty text", async () => {
+ statusAnswers({ status: "", user_id: " ", scopes: [] });
+ const { broker } = createArcadeClient("never-dialled");
+ const flow = broker.authorizations;
+ if (!flow) throw new Error("Arcade must declare an authorization flow.");
+
+ const narrowed = await flow.status("auth_123");
+ expect(narrowed.status).toBe("unknown");
+ expect(narrowed.userId).toBeNull();
+ });
+});
diff --git a/server/tests/arcade-adapter.test.ts b/server/tests/arcade-adapter.test.ts
new file mode 100644
index 000000000..d5ffb699c
--- /dev/null
+++ b/server/tests/arcade-adapter.test.ts
@@ -0,0 +1,291 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import Arcade, { APIError } from "@arcadeai/arcadejs";
+import { callTool, useArcadeClient } from "../src/plugins/arcade";
+import { BrokerRefusalError } from "../src/plugins/broker";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+
+/**
+ * Same pattern as `composio-adapter.test.ts` ("the key becoming a vendor"): `new Arcade({ apiKey })`
+ * opens no socket and sends no request, and its resources' methods live on their classes'
+ * prototypes, so a prototype replaced BEFORE `createArcadeClient` runs is what the client it builds
+ * will call. A seed instance is built only to reach the prototype.
+ */
+type Methods = Record unknown>;
+const toolsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).tools,
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ on: Methods,
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+) {
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+ useArcadeClient(null);
+});
+
+const toolOf = (toolkit: string, i: number) => ({
+ qualified_name: `${toolkit}.A${i}`,
+ toolkit: { name: toolkit, description: `${toolkit} things` },
+});
+
+describe("createArcadeClient", () => {
+ test("declares its grant is held by the vendor", () => {
+ expect(createArcadeClient("never-dialled").broker.grantIsLocal).toBe(false);
+ });
+
+ test("constructing it opens no socket and sends no request", () => {
+ const calls: string[] = [];
+ const realFetch = globalThis.fetch;
+ globalThis.fetch = ((...args: unknown[]) => {
+ calls.push(`fetch ${String(args[0])}`);
+ return Promise.reject(new Error("dialled"));
+ }) as typeof fetch;
+ restores.push(() => {
+ globalThis.fetch = realFetch;
+ });
+ for (const name of ["list", "execute", "get", "authorize"])
+ stub(toolsProto(), name, () => {
+ calls.push(`tools.${name}`);
+ return Promise.reject(new Error("dialled"));
+ });
+ const proto = Arcade.prototype as unknown as Methods;
+ for (const name of ["request", "get", "post", "getAPIList"])
+ stub(proto, name, () => {
+ calls.push(`client.${name}`);
+ return Promise.reject(new Error("dialled"));
+ });
+ createArcadeClient("never-dialled");
+ expect(calls).toEqual([]);
+ });
+
+ test("listApps counts actions across the FULL paged listing", async () => {
+ // Pages of 1000 — the directory's page size since the "Browse Arcade" deadline fix (see
+ // `DIRECTORY_PAGE_SIZE`); this pinned [0, 100, 200] at the old size of 100. Pages are served BY
+ // OFFSET, not by call order, because pages two and three are now asked for concurrently.
+ const pages: [string, number][][] = [
+ [["GithubApi", 1000]],
+ [["GithubApi", 1000]],
+ [
+ ["GithubApi", 400],
+ ["Github", 100],
+ ],
+ ];
+ const asked: { limit: number; offset: number }[] = [];
+ stub(toolsProto(), "list", (...args: unknown[]) => {
+ const q = args[0] as { limit: number; offset: number; toolkit?: string };
+ asked.push({ limit: q.limit, offset: q.offset });
+ const page = pages[q.offset / 1000] ?? [];
+ const items = page.flatMap(([tk, n]) =>
+ Array.from({ length: n }, (_, i) => toolOf(tk, i)),
+ );
+ return Promise.resolve({ items, total_count: 2500, offset: q.offset });
+ });
+ const apps = await createArcadeClient("never-dialled").broker.listApps();
+ expect(asked).toEqual([
+ { limit: 1000, offset: 0 },
+ { limit: 1000, offset: 1000 },
+ { limit: 1000, offset: 2000 },
+ ]);
+ expect(apps.find((a) => a.slug === "GithubApi")?.actionCount).toBe(2400);
+ expect(apps.find((a) => a.slug === "Github")?.actionCount).toBe(100);
+ expect(apps.reduce((n, a) => n + a.actionCount, 0)).toBe(2500);
+ });
+
+ test("a REAL APIError through execute reaches callTool's authorize sentence", async () => {
+ stub(toolsProto(), "execute", () =>
+ Promise.reject(
+ new APIError(
+ 403,
+ { name: "tool_authorization_required", message: "Authorize Gmail" },
+ undefined,
+ new Headers(),
+ ),
+ ),
+ );
+ const { actions } = createArcadeClient("never-dialled");
+ useArcadeClient(actions);
+ const result = await callTool(
+ { url: "arcade://Gmail", actorId: "u1" },
+ "Gmail.SendEmail",
+ {},
+ );
+ expect(result.isError).toBe(true);
+ expect(result.text).toBe(
+ "Gmail.SendEmail has not been authorized on this account yet. Authorize it in Settings → Connected accounts and ask again.",
+ );
+ });
+
+ test("a non-SDK error is rethrown as itself, not as a synthetic failure", async () => {
+ const boom = new Error("boom");
+ stub(toolsProto(), "execute", () => Promise.reject(boom));
+ const { actions } = createArcadeClient("never-dialled");
+ await expect(
+ actions.execute({ tool_name: "X.Y", input: {}, user_id: "u" }),
+ ).rejects.toBe(boom);
+ });
+
+ const connectionsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).admin.userConnections,
+ ) as Methods;
+ const authProvidersProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).admin.authProviders,
+ ) as Methods;
+ const account = { vendorUserId: "u1" } as never;
+
+ test("ensureAuthConfig answers not-needed in every case", async () => {
+ const { broker } = createArcadeClient("never-dialled");
+ for (const toolkit of ["Github", "Gmail", "GithubApi"])
+ expect(
+ await broker.ensureAuthConfig({
+ toolkit,
+ connection: { kind: "consent" },
+ } as never),
+ ).toBe("not-needed");
+ });
+
+ test("deleteAuthConfig is a no-op", async () => {
+ const { broker } = createArcadeClient("never-dialled");
+ expect(await broker.deleteAuthConfig("Github")).toBeUndefined();
+ });
+
+ test("connectionFields and connectWithFields refuse, naming the deployment-wide secret", async () => {
+ const { broker } = createArcadeClient("never-dialled");
+ const a = broker.connectionFields({
+ toolkit: "Github",
+ authScheme: "API_KEY",
+ } as never);
+ await expect(a).rejects.toBeInstanceOf(BrokerRefusalError);
+ await expect(a).rejects.toThrow(/deployment-wide secret/);
+ const b = broker.connectWithFields({
+ account,
+ toolkit: "Github",
+ authScheme: "API_KEY",
+ values: { k: "v" },
+ } as never);
+ await expect(b).rejects.toBeInstanceOf(BrokerRefusalError);
+ await expect(b).rejects.toThrow(/deployment-wide secret/);
+ });
+
+ test("accountName reads provider_user_info.name, then .login, else null", async () => {
+ const rows: unknown[] = [];
+ // A PAGE, as the real `list` resolves to: `connectionsFor` pages the listing itself rather
+ // than through the SDK, whose auto-pagination stops after page one when the engine omits
+ // `total_count`. See `arcade-pagination-engine.test.ts`.
+ stub(connectionsProto(), "list", () =>
+ Promise.resolve({ items: rows, offset: 0, total_count: rows.length }),
+ );
+ // The toolkit's own listing names the provider TYPE its connections are held under.
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({
+ items: [
+ {
+ qualified_name: "Github.A0",
+ requirements: { authorization: { id: "x", provider_id: "github" } },
+ },
+ ],
+ }),
+ );
+ // And the project's configured providers map that type to its INSTANCE ids, which is what a
+ // connection row carries. See `connectionsFor`.
+ stub(authProvidersProto(), "list", () =>
+ Promise.resolve({
+ items: [{ id: "cpk-poc", provider_id: "github" }],
+ total_count: 1,
+ }),
+ );
+ const { broker } = createArcadeClient("never-dialled");
+ const ask = () => broker.accountName({ account, toolkit: "Github" });
+ rows.push({
+ provider_id: "cpk-poc",
+ provider_user_info: { name: "Ada", login: "ada1" },
+ });
+ expect(await ask()).toBe("Ada");
+ rows.length = 0;
+ rows.push({
+ provider_id: "cpk-poc",
+ provider_user_info: { login: "ada1" },
+ });
+ expect(await ask()).toBe("ada1");
+ rows.length = 0;
+ rows.push({ provider_id: "cpk-poc", provider_user_info: {} });
+ expect(await ask()).toBeNull();
+
+ // AN EMPTY NAME MUST NOT SHADOW A GOOD LOGIN. `??` falls through on null and undefined only,
+ // so a `textOf` that answered `""` as text ended the chain on the empty field and drew a blank
+ // where the account's identity goes — on a connection that is genuinely connected.
+ rows.length = 0;
+ rows.push({
+ provider_id: "cpk-poc",
+ provider_user_info: { name: "", login: "maxim" },
+ });
+ expect(await ask()).toBe("maxim");
+
+ // Whitespace-only is the same blank to whoever reads the screen.
+ rows.length = 0;
+ rows.push({
+ provider_id: "cpk-poc",
+ provider_user_info: { name: " ", login: "maxim" },
+ });
+ expect(await ask()).toBe("maxim");
+
+ // And a row carrying nothing but empty text is no name at all, never an empty one.
+ rows.length = 0;
+ rows.push({
+ provider_id: "cpk-poc",
+ provider_user_info: { name: "", login: "" },
+ });
+ expect(await ask()).toBeNull();
+ });
+
+ test("a toolkit named with empty text raises no blank app", async () => {
+ stub(toolsProto(), "list", (...args: unknown[]) => {
+ const q = args[0] as { offset: number };
+ return Promise.resolve({
+ items:
+ q.offset === 0
+ ? [
+ toolOf("Github", 0),
+ { qualified_name: "X.A0", toolkit: { name: "" } },
+ { qualified_name: "Y.A0", toolkit: { name: " " } },
+ ]
+ : [],
+ offset: q.offset,
+ });
+ });
+ const apps = await createArcadeClient("never-dialled").broker.listApps();
+ // The slug is what a URL and a picker row are built from; an empty one is unconnectable.
+ expect(apps.map((a) => a.slug)).toEqual(["Github"]);
+ });
+
+ test("no app resolves to a fields connection", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({
+ items: [toolOf("Github", 0), toolOf("Gmail", 0)],
+ total_count: 2,
+ }),
+ );
+ const apps = await createArcadeClient("never-dialled").broker.listApps();
+ expect(apps.length).toBeGreaterThan(0);
+ // `no-auth` is in the list because `toolOf` publishes no `requirements` at all, which is Arcade
+ // saying this action needs authorizing by nobody — the kind is READ per app now rather than
+ // written once for the catalogue, and `arcade-connection-kind-engine.test.ts` is where each
+ // published shape is held to its answer. `fields` is the one this test is about and it is still
+ // unreachable: Arcade has no per-person field entry.
+ for (const app of apps)
+ expect(["consent", "no-auth", "unsupported"]).toContain(
+ app.connection.kind,
+ );
+ });
+});
diff --git a/server/tests/arcade-attempt-constraints.test.ts b/server/tests/arcade-attempt-constraints.test.ts
new file mode 100644
index 000000000..e5fa4633a
--- /dev/null
+++ b/server/tests/arcade-attempt-constraints.test.ts
@@ -0,0 +1,399 @@
+// server/tests/arcade-attempt-constraints.test.ts
+//
+// `pending_authorizations` declares two unions in TypeScript — `holder` and `outcome` — and
+// drizzle's `$type<>` is erased at runtime, so only the database can hold them. These are the
+// checks the sibling table `brokered_connections` has had all along, asked of the new table:
+// a holder outside the pair, an outcome outside the roster, and the two holder/`user_id`
+// combinations `brokered_connections_holder_check` exists to make impossible.
+import { afterAll, beforeAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { sql } from "drizzle-orm";
+import { createDatabase } from "../src/db/client";
+import { mcpServers, pendingAuthorizations } from "../src/db/schema/plugins";
+import { checkRoster } from "./support/constraints";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+const serverId = `arcade-constraints-${suite}`;
+
+/**
+ * An attempt row with every column legal, so each test varies exactly the one it is about.
+ *
+ * `userId` defaults to the actor, because a `person` attempt is for the actor's own account and the
+ * database holds that (`pending_authorizations_user_is_actor_check`). This fixture used to give
+ * every row one shared `user-${suite}` beside a fresh actor — a two-person row production never
+ * writes, and exactly the shape that let one person's deletion take another person's attempt.
+ */
+const attempt = (overrides: Record) => {
+ const actorId =
+ (overrides.actorId as string | undefined) ??
+ `actor-${randomUUID().slice(0, 8)}`;
+ return {
+ actorId,
+ serverId,
+ action: "connect",
+ holder: "person" as const,
+ userId: actorId,
+ vendorUserId: `v-${suite}`,
+ ...overrides,
+ };
+};
+
+/**
+ * Every person this file invented, so `afterAll` can take them away again.
+ *
+ * `actor_id` keys to `users` and cascades (and `user_id`, when set, must equal it), so the row a
+ * case is about cannot be written until its person exists — and deleting the person afterwards is
+ * also what deletes the attempt. Seeding happens inside {@link insert} rather than in `beforeAll` because the actor id is
+ * generated per row, and a case that forgot to seed its own would otherwise be refused by the
+ * foreign key and read as a CHECK that did not fire.
+ */
+const seeded = new Set();
+
+const seedFor = async (row: Record) => {
+ const people = [row.actorId, row.userId].filter(
+ (id): id is string => typeof id === "string" && id.length > 0,
+ );
+ for (const id of people) seeded.add(id);
+ await seedPeople(database, people);
+};
+
+// Awaited rather than handed back as a builder, so a failure says what the database did with the
+// row and not what drizzle's query object looks like.
+const insert = async (overrides: Record) => {
+ const row = attempt(overrides);
+ await seedFor(row);
+ await database
+ .insert(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: the point is what the DATABASE refuses, past the declared unions.
+ .values(row as any);
+};
+
+/**
+ * The name of the constraint that refused a statement, or `"accepted"` if nothing did.
+ *
+ * Named rather than merely thrown, because "it threw" would also pass on a typo in a column name.
+ * drizzle's own message is only "Failed query: ..."; the constraint it tripped is `constraint` on
+ * the postgres error underneath.
+ */
+const refusedBy = async (run: () => Promise): Promise => {
+ try {
+ await run();
+ } catch (error) {
+ const cause = (error as { cause?: { constraint?: string } }).cause;
+ return cause?.constraint ?? String(error);
+ }
+ return "accepted";
+};
+
+beforeAll(async () => {
+ await database.insert(mcpServers).values({
+ id: serverId,
+ title: "Attempt constraints",
+ vendor: "test",
+ url: "https://example.invalid/mcp",
+ });
+});
+
+afterAll(async () => {
+ await database.execute(sql`delete from mcp_servers where id = ${serverId}`);
+ await removePeople(database, [...seeded]);
+ await database.$client.close();
+});
+
+describe("pending_authorizations holds its declared unions", () => {
+ test("a holder outside the pair is refused", async () => {
+ expect(await refusedBy(() => insert({ holder: "llama" }))).toBe(
+ "pending_authorizations_holder_check",
+ );
+ });
+
+ /*
+ * Each roster case carries a `completed_at` alongside its junk `outcome`, so the row is legal in
+ * every respect but the one under test. Without it the row also breaks the completion pairing
+ * below, postgres names whichever constraint it evaluated first, and the test stops being about
+ * the roster at all.
+ */
+ test("an outcome outside the roster is refused on insert", async () => {
+ expect(
+ await refusedBy(() =>
+ insert({ outcome: "banana", completedAt: new Date() }),
+ ),
+ ).toBe("pending_authorizations_outcome_check");
+ });
+
+ test("an outcome outside the roster is refused on update", async () => {
+ const actorId = `update-${suite}`;
+ await insert({ actorId });
+ const refusal = await refusedBy(() =>
+ database
+ .update(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: a cast past the union is exactly what the CHECK is for.
+ .set({ outcome: "cancelled" as any, completedAt: new Date() })
+ .where(sql`actor_id = ${actorId}`),
+ );
+ expect(refusal).toBe("pending_authorizations_outcome_check");
+ });
+
+ /**
+ * The pairing of `completed_at` and `outcome`, which is the one invariant of this table that no
+ * single column can hold.
+ *
+ * A row carrying an `outcome` with `completed_at` still NULL is a FINISHED attempt that every
+ * reader here reads as a LIVE one: `claimAuthorizationAttempt` replaces a held key only
+ * `WHERE created_at < now() - interval '15 minutes' OR completed_at IS NOT NULL`, and
+ * `authorizationAttemptFor` answers on `completed_at IS NULL` alone. Neither looks at `outcome`,
+ * so such a row locks its actor out of starting another authorization for the rest of the TTL,
+ * with a verdict already recorded against it. The other half is less damaging and just as wrong:
+ * a `completed_at` with no `outcome` is an attempt that ended with nothing saying how, and every
+ * operator-facing reading of the table is of `outcome`.
+ *
+ * Nothing in the application writes either shape — both writers set the pair in one statement —
+ * which is exactly why it belongs in the database rather than in a comment: the only ways in are
+ * a hand-run UPDATE, a restore, or the next writer, and none of those is reviewed here.
+ */
+ test("an outcome with no completion time is refused", async () => {
+ expect(
+ await refusedBy(() => insert({ outcome: "failed", completedAt: null })),
+ ).toBe("pending_authorizations_completion_check");
+ });
+
+ test("a completion time with no outcome is refused", async () => {
+ expect(
+ await refusedBy(() => insert({ completedAt: new Date(), outcome: null })),
+ ).toBe("pending_authorizations_completion_check");
+ });
+
+ test("breaking the pair by UPDATE is refused too", async () => {
+ const actorId = `pair-${suite}`;
+ await insert({ actorId });
+ const refusal = await refusedBy(() =>
+ database
+ .update(pendingAuthorizations)
+ .set({ outcome: "withdrawn" })
+ .where(sql`actor_id = ${actorId}`),
+ );
+ expect(refusal).toBe("pending_authorizations_completion_check");
+ });
+
+ test("a person attempt with no user id is refused", async () => {
+ expect(
+ await refusedBy(() => insert({ holder: "person", userId: null })),
+ ).toBe("pending_authorizations_holder_check");
+ });
+
+ test("a deployment attempt carrying a user id is refused", async () => {
+ expect(
+ await refusedBy(() =>
+ // The actor's own id, so the user-is-actor CHECK is satisfied and only the holder pair can
+ // be what refuses.
+ insert({ holder: "deployment" }),
+ ),
+ ).toBe("pending_authorizations_holder_check");
+ });
+
+ /**
+ * The rosters themselves, because every test above asks only about one value outside them.
+ *
+ * `llama` and `banana` go on being refused by a constraint that has quietly grown a member, and
+ * `cancelled` goes on being refused right up until the day it is the member that was added — at
+ * which point that test fails for the right reason and these two say which roster moved. The
+ * TypeScript unions in `schema/plugins` are erased at runtime, so these lines and the migration
+ * are the only two places the rosters exist; they have to be read against each other.
+ */
+ test("the holder CHECK admits exactly the two holders", async () => {
+ expect(
+ await checkRoster(database, "pending_authorizations_holder_check"),
+ ).toEqual(["deployment", "person"]);
+ });
+
+ test("the outcome CHECK admits exactly the four outcomes", async () => {
+ expect(
+ await checkRoster(database, "pending_authorizations_outcome_check"),
+ ).toEqual(["completed", "failed", "unreconciled", "withdrawn"]);
+ });
+
+ test("the legal shapes are all still accepted", async () => {
+ const action = `legal-${suite}`;
+ await insert({ action, holder: "person" });
+ await insert({ action, holder: "deployment", userId: null });
+ // Each ended shape carries its `completed_at`, because the pair is now held by a CHECK: an
+ // outcome on its own is no longer one of the legal shapes, and this list says which are.
+ for (const outcome of [
+ null,
+ "completed",
+ "failed",
+ "unreconciled",
+ "withdrawn",
+ ]) {
+ await insert({
+ action,
+ outcome,
+ completedAt: outcome === null ? null : new Date(),
+ });
+ }
+ const rows = await database.execute(
+ sql`select count(*)::int as n from pending_authorizations where action = ${action}`,
+ );
+ expect(rows[0]?.n).toBe(7);
+ });
+});
+
+/**
+ * The two columns that name `users.id`, and whether the database holds them to it.
+ *
+ * `0057` created both `actor_id` and `user_id` naming `users.id` in the docblock and carrying no
+ * key, which is the shape where the schema is documentation and the data is whatever got written:
+ * a deleted person left attempts pointing at nobody, and an actor id that was never a person at all
+ * was accepted without complaint. `0059` adds both keys, cascading, and these are the cases that
+ * say so — asked of the DATABASE, because a `.references()` in drizzle that no migration carried
+ * into postgres enforces nothing at all.
+ */
+describe("pending_authorizations keys the people it names", () => {
+ test("an actor who is not a person is refused", async () => {
+ const stranger = `ghost-actor-${randomUUID().slice(0, 8)}`;
+ const refusal = await refusedBy(() =>
+ database
+ .insert(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: an id past the key is exactly what the FK is for.
+ .values(attempt({ actorId: stranger, userId: stranger }) as any),
+ );
+ expect(refusal).toBe("pending_authorizations_actor_id_users_id_fk");
+ });
+
+ test("a user_id that is not a person is refused", async () => {
+ // The actor IS a person here, so only the second column can be what refuses: a case that left
+ // both unseeded would pass on the actor key alone and say nothing about `user_id`. Since `0062`
+ // what refuses it is the user-is-actor CHECK, not a key of its own: `user_id` names a person
+ // exactly when it names the actor, who is one.
+ const actorId = `keyed-actor-${randomUUID().slice(0, 8)}`;
+ await seedPeople(database, [actorId]);
+ seeded.add(actorId);
+ const refusal = await refusedBy(() =>
+ database.insert(pendingAuthorizations).values(
+ attempt({
+ actorId,
+ userId: `ghost-owner-${randomUUID().slice(0, 8)}`,
+ // biome-ignore lint/suspicious/noExplicitAny: an id that names nobody is exactly what is being asked about.
+ }) as any,
+ ),
+ );
+ expect(refusal).toBe("pending_authorizations_user_is_actor_check");
+ });
+
+ test("deleting the person takes their in-flight attempt with them", async () => {
+ const actorId = `departing-${randomUUID().slice(0, 8)}`;
+ await insert({ actorId, userId: actorId });
+ const before = await database.execute(
+ sql`select count(*)::int as n from pending_authorizations where actor_id = ${actorId}`,
+ );
+ expect(before[0]?.n).toBe(1);
+
+ await database.execute(sql`delete from users where id = ${actorId}`);
+ seeded.delete(actorId);
+
+ const after = await database.execute(
+ sql`select count(*)::int as n from pending_authorizations where actor_id = ${actorId}`,
+ );
+ expect(after[0]?.n).toBe(0);
+ });
+
+ /**
+ * The index, not merely the key.
+ *
+ * Postgres indexes the REFERENCED side of a foreign key and not the referencing side, so the
+ * `server_id` key `0057` added left every cascade from `mcp_servers` — and every lookup by server
+ * — scanning this table. There is nothing about a missing index a functional test can observe, so
+ * it is asked for by name.
+ *
+ * The keys are read from `pg_constraint` rather than listed, so a key added later is held to the
+ * same rule without anybody remembering this test — and the list is pinned too, so dropping or
+ * adding one is a visible edit. `user_id` lost its key (and its index) in `0062`; see the
+ * user-is-actor cases below.
+ */
+ test("every foreign key on the table has an index leading with its column", async () => {
+ const keys = await database.execute(sql`
+ SELECT a.attname AS column
+ FROM pg_constraint c
+ JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
+ WHERE c.conrelid = 'pending_authorizations'::regclass AND c.contype = 'f'
+ ORDER BY a.attname
+ `);
+ const columns = keys.map((row) => String(row.column));
+ const rows = await database.execute(sql`
+ SELECT indexdef FROM pg_indexes
+ WHERE schemaname = 'public' AND tablename = 'pending_authorizations'
+ `);
+ const definitions = rows.map((row) => String(row.indexdef));
+ const led = (column: string) =>
+ definitions.some((definition) =>
+ definition.includes(`USING btree (${column}`),
+ );
+ expect(
+ Object.fromEntries(columns.map((column) => [column, led(column)])),
+ ).toEqual({ actor_id: true, server_id: true });
+ });
+});
+
+/**
+ * Whose deletion may take an attempt away: the person who STARTED it, and nobody else.
+ *
+ * The row is keyed by `actor_id` — one attempt per actor, by primary key — so it is the actor's.
+ * `user_id` is whose account the attempt is for, and for a `person` attempt that is always the
+ * actor too: `accountRefFor` answers a Personal app with `{ userId: actorId }`, and the only writer
+ * (`claimAuthorizationAttempt`) copies that ref. Before `0062` nothing in the DATABASE said so, and
+ * `user_id` carried a cascading key of its own — so a row naming two people died with EITHER of
+ * them, and one person's deletion destroyed an attempt somebody else had started.
+ *
+ * The first case is the observation itself, asked without a branch: try to write the two-person
+ * row, then — whatever the database did with it — give the actor their own attempt if the slot is
+ * still free (`ON CONFLICT DO NOTHING`), delete the OTHER person, and count what the actor has
+ * left. Against a schema that admits the two-person row, the actor's attempt is gone.
+ */
+describe("an attempt belongs to the person who started it", () => {
+ test("deleting one person never takes an attempt a different person started", async () => {
+ const actorId = `starter-${randomUUID().slice(0, 8)}`;
+ const otherId = `other-${randomUUID().slice(0, 8)}`;
+ await seedPeople(database, [actorId, otherId]);
+ seeded.add(actorId);
+ seeded.add(otherId);
+
+ await refusedBy(() =>
+ database
+ .insert(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: the two-person row is exactly what is being asked about.
+ .values(attempt({ actorId, userId: otherId }) as any),
+ );
+ await database
+ .insert(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: a legal attempt, written past the declared unions like every row here.
+ .values(attempt({ actorId, userId: actorId }) as any)
+ .onConflictDoNothing();
+
+ await database.execute(sql`delete from users where id = ${otherId}`);
+ seeded.delete(otherId);
+
+ const left = await database.execute(
+ sql`select count(*)::int as n from pending_authorizations where actor_id = ${actorId}`,
+ );
+ expect(left[0]?.n).toBe(1);
+ });
+
+ test("a person attempt for somebody other than its actor is refused", async () => {
+ const actorId = `starter-${randomUUID().slice(0, 8)}`;
+ const otherId = `other-${randomUUID().slice(0, 8)}`;
+ await seedPeople(database, [actorId, otherId]);
+ seeded.add(actorId);
+ seeded.add(otherId);
+ expect(
+ await refusedBy(() =>
+ database
+ .insert(pendingAuthorizations)
+ // biome-ignore lint/suspicious/noExplicitAny: the two-person row is exactly what is being asked about.
+ .values(attempt({ actorId, userId: otherId }) as any),
+ ),
+ ).toBe("pending_authorizations_user_is_actor_check");
+ });
+});
diff --git a/server/tests/arcade-attempt-schema.test.ts b/server/tests/arcade-attempt-schema.test.ts
new file mode 100644
index 000000000..5c8c05879
--- /dev/null
+++ b/server/tests/arcade-attempt-schema.test.ts
@@ -0,0 +1,92 @@
+// server/tests/arcade-attempt-schema.test.ts
+import { afterAll, beforeAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { sql } from "drizzle-orm";
+import { createDatabase } from "../src/db/client";
+import { mcpServers } from "../src/db/schema/plugins";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+const serverId = `arcade-attempt-${suite}`;
+
+/*
+ * The three actors the cases below claim under, seeded as real people because `actor_id` now carries
+ * the key to `users` it always claimed in the docblock (and `user_id` is held equal to it). Named up here so
+ * `afterAll` can delete them, and so the cascade takes the attempts with them.
+ */
+const actors = [`live-${suite}`, `expired-${suite}`, `done-${suite}`];
+
+/** The one statement a start-attempt route issues: take the key if free, expired, or completed. */
+const claim = (actorId: string, authId: string) =>
+ database.execute(sql`
+ INSERT INTO pending_authorizations
+ (actor_id, server_id, action, holder, user_id, vendor_user_id, auth_id)
+ VALUES (${actorId}, ${serverId}, 'connect', 'person', ${actorId}, ${`v-${actorId}`}, ${authId})
+ ON CONFLICT (actor_id) DO UPDATE SET
+ server_id = EXCLUDED.server_id,
+ action = EXCLUDED.action,
+ holder = EXCLUDED.holder,
+ user_id = EXCLUDED.user_id,
+ vendor_user_id = EXCLUDED.vendor_user_id,
+ auth_id = EXCLUDED.auth_id,
+ created_at = now(),
+ completed_at = NULL,
+ outcome = NULL
+ WHERE pending_authorizations.created_at < now() - interval '15 minutes'
+ OR pending_authorizations.completed_at IS NOT NULL
+ RETURNING *
+ `);
+
+beforeAll(async () => {
+ await seedPeople(database, actors);
+ await database.insert(mcpServers).values({
+ id: serverId,
+ title: "Attempt schema",
+ vendor: "test",
+ url: "https://example.invalid/mcp",
+ });
+});
+
+afterAll(async () => {
+ await database.execute(sql`delete from mcp_servers where id = ${serverId}`);
+ await removePeople(database, actors);
+ await database.$client.close();
+});
+
+describe("pending_authorizations", () => {
+ test("a second attempt for one actor is refused while the first is live", async () => {
+ const actor = actors[0]!;
+ expect((await claim(actor, "auth-1")).length).toBe(1);
+ expect((await claim(actor, "auth-2")).length).toBe(0);
+ const rows = await database.execute(
+ sql`select auth_id from pending_authorizations where actor_id = ${actor}`,
+ );
+ expect(rows[0]?.auth_id).toBe("auth-1");
+ });
+
+ test("an expired attempt is replaceable in one statement", async () => {
+ const actor = actors[1]!;
+ expect((await claim(actor, "auth-old")).length).toBe(1);
+ await database.execute(
+ sql`update pending_authorizations set created_at = now() - interval '16 minutes' where actor_id = ${actor}`,
+ );
+ const replaced = await claim(actor, "auth-new");
+ expect(replaced.length).toBe(1);
+ expect(replaced[0]?.auth_id).toBe("auth-new");
+ });
+
+ test("a completed attempt is replaceable", async () => {
+ const actor = actors[2]!;
+ expect((await claim(actor, "auth-old")).length).toBe(1);
+ await database.execute(
+ sql`update pending_authorizations set completed_at = now(), outcome = 'completed' where actor_id = ${actor}`,
+ );
+ const replaced = await claim(actor, "auth-new");
+ expect(replaced.length).toBe(1);
+ expect(replaced[0]?.auth_id).toBe("auth-new");
+ expect(replaced[0]?.completed_at).toBeNull();
+ expect(replaced[0]?.outcome).toBeNull();
+ });
+});
diff --git a/server/tests/arcade-attempt-store.integration.test.ts b/server/tests/arcade-attempt-store.integration.test.ts
new file mode 100644
index 000000000..4874aa834
--- /dev/null
+++ b/server/tests/arcade-attempt-store.integration.test.ts
@@ -0,0 +1,504 @@
+// server/tests/arcade-attempt-store.integration.test.ts
+import { afterAll, beforeAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { eq, sql } from "drizzle-orm";
+import { createAuditStore } from "../src/audit";
+import { createDatabase } from "../src/db/client";
+import { mcpServers, pendingAuthorizations } from "../src/db/schema";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+import { createPluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+const serverId = `arcade-attempt-store-${suite}`;
+
+const broker: ConnectedAppBroker = {
+ // Arcade holds the grant at the vendor, not locally; see `ConnectedAppBroker.grantIsLocal`.
+ grantIsLocal: false,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ // Arcade runs the correlated, pollable flow, so this stub DECLARES the sub-surface rather
+ // than a null; nothing here reaches either member. See `ConnectedAppBroker.authorizations`.
+ authorizations: {
+ status: async () => {
+ throw new Error("nothing here polls an authorization");
+ },
+ confirmReturn: async () => {
+ throw new Error("nothing here confirms a return leg");
+ },
+ },
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async () => ({
+ redirectUrl: "https://vendor.example/consent",
+ authorizationId: "ar_stub",
+ }),
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => "acme-bot",
+};
+
+const policy = { mode: "enforce" as const, deny: [], allow: ["true"] };
+
+const store = createPluginStore({
+ brokerProvider: "arcade",
+ database,
+ auditStore: createAuditStore(database),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => policy,
+ broker,
+ deploymentId: `acme-${suite}`,
+ sharedUse: async () => ({ allowed: true }),
+});
+
+const account = (actor: string): AccountRef => ({
+ holder: "person",
+ userId: actor,
+ vendorUserId: `v-${actor}`,
+});
+
+const claim = (actorId: string, action = "connect") =>
+ store.claimAuthorizationAttempt({
+ actorId,
+ serverId,
+ action,
+ account: account(actorId),
+ });
+
+const age = (actorId: string, interval: string) =>
+ database.execute(
+ sql`update pending_authorizations set created_at = created_at - ${interval}::interval where actor_id = ${actorId}`,
+ );
+
+/**
+ * An actor id for this suite, AND THE PERSON BEHIND IT.
+ *
+ * `pending_authorizations.actor_id` keys to `users` and cascades (`.user_id` must equal it), so an
+ * attempt cannot be written for somebody who does not exist. Seeding here rather than in each case
+ * keeps the cases about the store: a case that invented its own id would be refused by the foreign
+ * key, and the refusal would read as the store failing to claim.
+ */
+const actors: string[] = [];
+const actor = async (name: string) => {
+ const id = `${name}-${suite}`;
+ actors.push(id);
+ await seedPeople(database, [id]);
+ return id;
+};
+
+beforeAll(async () => {
+ await database.insert(mcpServers).values({
+ id: serverId,
+ title: "Attempt store",
+ vendor: "Arcade",
+ url: "https://example.invalid/mcp",
+ });
+});
+
+afterAll(async () => {
+ // Cascades to the attempts.
+ await database.delete(mcpServers).where(eq(mcpServers.id, serverId));
+ await removePeople(database, actors);
+ await database.$client.close();
+});
+
+describe("claimAuthorizationAttempt", () => {
+ test("succeeds when no attempt exists", async () => {
+ const a = await actor("fresh");
+ const got = await claim(a);
+ expect(got.claimed).toBe(true);
+ if (!got.claimed) return;
+ expect(got.attempt.actorId).toBe(a);
+ expect(got.attempt.serverId).toBe(serverId);
+ expect(got.attempt.holder).toBe("person");
+ expect(got.attempt.userId).toBe(a);
+ expect(got.attempt.vendorUserId).toBe(`v-${a}`);
+ expect(got.attempt.authId).toBeNull();
+ });
+
+ test("a second claim while the first is live returns zero rows and answers the live row", async () => {
+ const a = await actor("live");
+ const first = await claim(a, "connect");
+ const second = await claim(a, "reconnect");
+ expect(first.claimed).toBe(true);
+ expect(second.claimed).toBe(false);
+ if (first.claimed && !second.claimed) {
+ expect(second.live?.actorId).toBe(a);
+ expect(second.live?.action).toBe("connect");
+ expect(second.live?.createdAt.getTime()).toBe(
+ first.attempt.createdAt.getTime(),
+ );
+ }
+ });
+
+ test("succeeds after the first expired, replacing it", async () => {
+ const a = await actor("expired");
+ await claim(a, "old");
+ await age(a, "16 minutes");
+ const got = await claim(a, "new");
+ expect(got.claimed).toBe(true);
+ const rows = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, a));
+ expect(rows.length).toBe(1);
+ expect(rows[0]?.action).toBe("new");
+ });
+
+ test("succeeds after the first completed, replacing it", async () => {
+ const a = await actor("completed");
+ await claim(a, "old");
+ await database.execute(
+ sql`update pending_authorizations set completed_at = now(), outcome = 'completed' where actor_id = ${a}`,
+ );
+ const got = await claim(a, "new");
+ expect(got.claimed).toBe(true);
+ const rows = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, a));
+ expect(rows[0]?.action).toBe("new");
+ expect(rows[0]?.completedAt).toBeNull();
+ expect(rows[0]?.outcome).toBeNull();
+ });
+
+ test("two concurrent claims by one actor: exactly one wins", async () => {
+ const a = await actor("race");
+ const results = await Promise.all([claim(a, "one"), claim(a, "two")]);
+ expect(results.filter((r) => r.claimed).length).toBe(1);
+ expect(results.filter((r) => !r.claimed).length).toBe(1);
+ });
+});
+
+describe("recordAuthorizationMint", () => {
+ test("writes the ar_ onto the attempt and leaves another actor's alone", async () => {
+ const a = await actor("mint");
+ const other = await actor("mint-other");
+ const mine = await claim(a);
+ await claim(other);
+ if (!mine.claimed) throw new Error("claim failed");
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: mine.attempt.createdAt,
+ authId: "ar_abc",
+ });
+ expect(wrote).toBe(true);
+ const got = await store.authorizationAttemptFor(a);
+ expect(got?.authId).toBe("ar_abc");
+ expect((await store.authorizationAttemptFor(other))?.authId).toBeNull();
+ });
+
+ test("a mint against a different created_at writes nothing", async () => {
+ const a = await actor("mint-stale");
+ await claim(a);
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: new Date(0),
+ authId: "ar_stale",
+ });
+ expect(wrote).toBe(false);
+ expect((await store.authorizationAttemptFor(a))?.authId).toBeNull();
+ });
+
+ /**
+ * The attempt's `auth_id` as the TABLE holds it. Not `authorizationAttemptFor`, which answers
+ * null for a settled attempt and so could not see a settled row being rewritten.
+ */
+ const storedAuthId = async (actorId: string) => {
+ const [row] = await database
+ .select({ authId: pendingAuthorizations.authId })
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, actorId));
+ if (!row) throw new Error(`no attempt for ${actorId}`);
+ return row.authId;
+ };
+
+ /** Claim and mint one attempt, answering the `created_at` it is named by. */
+ const minted = async (actorId: string, authId: string) => {
+ const got = await claim(actorId);
+ if (!got.claimed) throw new Error("claim failed");
+ const wrote = await store.recordAuthorizationMint({
+ actorId,
+ createdAt: got.attempt.createdAt,
+ authId,
+ });
+ if (!wrote) throw new Error("first mint failed");
+ return got.attempt.createdAt;
+ };
+
+ test("a late mint cannot rewrite a COMPLETED attempt's auth_id to null", async () => {
+ const a = await actor("mint-late-null");
+ const createdAt = await minted(a, "ar_consented");
+ await database.execute(
+ sql`update pending_authorizations set completed_at = now(), outcome = 'completed' where actor_id = ${a}`,
+ );
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt,
+ authId: null,
+ });
+ expect(await storedAuthId(a)).toBe("ar_consented");
+ expect(wrote).toBe(false);
+ });
+
+ test("a late mint cannot rebind a FAILED attempt to a different ar_", async () => {
+ const a = await actor("mint-late-other");
+ const createdAt = await minted(a, "ar_consented");
+ expect(
+ await store.failAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_consented",
+ }),
+ ).toBe(true);
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt,
+ authId: "ar_other",
+ });
+ expect(await storedAuthId(a)).toBe("ar_consented");
+ expect(wrote).toBe(false);
+ });
+
+ test("a duplicated mint cannot rebind a LIVE attempt that already holds an ar_", async () => {
+ const a = await actor("mint-dup");
+ const createdAt = await minted(a, "ar_first");
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt,
+ authId: "ar_second",
+ });
+ expect(await storedAuthId(a)).toBe("ar_first");
+ expect(wrote).toBe(false);
+ });
+
+ test("a mint cannot bind an attempt settled with no auth_id by a writer outside the store", async () => {
+ const a = await actor("mint-late-settled-null");
+ const got = await claim(a);
+ if (!got.claimed) throw new Error("claim failed");
+ await database.execute(
+ sql`update pending_authorizations set completed_at = now(), outcome = 'failed' where actor_id = ${a}`,
+ );
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: got.attempt.createdAt,
+ authId: "ar_late",
+ });
+ expect(await storedAuthId(a)).toBeNull();
+ expect(wrote).toBe(false);
+ });
+
+ test("a mint still writes onto a live attempt whose vendor answered no id", async () => {
+ const a = await actor("mint-after-null");
+ const got = await claim(a);
+ if (!got.claimed) throw new Error("claim failed");
+ expect(
+ await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: got.attempt.createdAt,
+ authId: null,
+ }),
+ ).toBe(true);
+ expect(await storedAuthId(a)).toBeNull();
+ });
+});
+
+describe("discardAuthorizationAttempt", () => {
+ test("deletes the named attempt, an unminted one included", async () => {
+ const a = await actor("discard");
+ const got = await claim(a);
+ if (!got.claimed) throw new Error("claim failed");
+ const deleted = await store.discardAuthorizationAttempt({
+ actorId: a,
+ createdAt: got.attempt.createdAt,
+ });
+ expect(deleted).toBe(true);
+ expect(await store.authorizationAttemptFor(a)).toBeNull();
+ });
+
+ test("leaves a NEWER attempt by the same actor standing", async () => {
+ const a = await actor("newer");
+ const older = await claim(a, "older");
+ if (!older.claimed) throw new Error("claim failed");
+ await age(a, "16 minutes");
+ // What the row NOW holds, which is what a late discard would have been aiming at.
+ // `older.attempt.createdAt` is the PRE-age value and matches nothing, so a discard
+ // aimed at it misses whether or not a replacement exists, which is no test at all.
+ const [aged] = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, a));
+ if (!aged) throw new Error("aged row missing");
+ const agedAt = aged.createdAt;
+
+ const newer = await claim(a, "newer");
+ expect(newer.claimed).toBe(true);
+ if (!newer.claimed) return;
+ // Differ by 16 minutes BY CONSTRUCTION, not by wall-clock luck: no same-millisecond race.
+ expect(newer.attempt.createdAt.getTime()).not.toBe(agedAt.getTime());
+
+ // Aimed at the attempt that actually stood: the replacement must survive it.
+ const deleted = await store.discardAuthorizationAttempt({
+ actorId: a,
+ createdAt: agedAt,
+ });
+ expect(deleted).toBe(false);
+ const standing = await store.authorizationAttemptFor(a);
+ expect(standing?.action).toBe("newer");
+ expect(standing?.createdAt.getTime()).toBe(
+ newer.attempt.createdAt.getTime(),
+ );
+ });
+
+ /*
+ * B2a. A SETTLED ATTEMPT IS A RECORD, AND A DISCARD AIMED AT IT BY ITS OWN `created_at` MUST NOT
+ * ERASE IT. Every caller reads the attempt first and discards it after, so the `created_at` it
+ * holds still names the row once a completion has committed in between.
+ */
+ for (const outcome of [
+ "completed",
+ "failed",
+ "unreconciled",
+ "withdrawn",
+ ] as const) {
+ test(`leaves a ${outcome} attempt standing and answers false`, async () => {
+ const a = await actor(`discard-settled-${outcome}`);
+ const got = await claim(a);
+ if (!got.claimed) throw new Error("claim failed");
+ await database.execute(
+ sql`update pending_authorizations set completed_at = now(), outcome = ${outcome} where actor_id = ${a}`,
+ );
+
+ const deleted = await store.discardAuthorizationAttempt({
+ actorId: a,
+ createdAt: got.attempt.createdAt,
+ });
+
+ const rows = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, a));
+ expect(rows.map((row) => row.outcome)).toEqual([outcome]);
+ expect(deleted).toBe(false);
+ expect(
+ await store.settledAuthorizationOutcome({
+ actorId: a,
+ createdAt: got.attempt.createdAt,
+ }),
+ ).toBe(outcome);
+ });
+ }
+});
+
+describe("authorizationAttemptFor", () => {
+ test("answers the live attempt", async () => {
+ const a = await actor("for-live");
+ await claim(a);
+ expect((await store.authorizationAttemptFor(a))?.actorId).toBe(a);
+ });
+
+ test("answers null when there is none", async () => {
+ expect(
+ await store.authorizationAttemptFor(await actor("for-none")),
+ ).toBeNull();
+ });
+
+ test("answers null past the TTL", async () => {
+ const a = await actor("for-expired");
+ await claim(a);
+ await age(a, "16 minutes");
+ expect(await store.authorizationAttemptFor(a)).toBeNull();
+ });
+});
+
+/**
+ * Naming an attempt by its `created_at` has to work for a row ANY writer wrote, and for a value
+ * that has been through JSON on the way back.
+ *
+ * `claimAuthorizationAttempt` truncates what it writes to milliseconds, but the COLUMN DEFAULT is
+ * `now()`, which PostgreSQL keeps to the microsecond; a row that takes the default — which is what
+ * drizzle's inferred insert type invites — is read back as a millisecond `Date` naming a different
+ * instant than the one stored. An exact `=` then matches nothing and the attempt is silently dead:
+ * no `auth_id` is ever written, and per the table's own docblock "an attempt holding null can never
+ * complete".
+ */
+describe("naming an attempt by created_at", () => {
+ /** Takes the column default, the way any writer using the inferred insert type does. */
+ const insertWithDefaultCreatedAt = async (actorId: string) => {
+ const [row] = await database
+ .insert(pendingAuthorizations)
+ .values({
+ actorId,
+ serverId,
+ action: "connect",
+ holder: "person",
+ userId: actorId,
+ vendorUserId: `v-${actorId}`,
+ })
+ .returning();
+ if (!row) throw new Error("insert returned nothing");
+ return row;
+ };
+
+ test("a mint matches a row written with the column default", async () => {
+ const a = await actor("default-mint");
+ const row = await insertWithDefaultCreatedAt(a);
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: row.createdAt,
+ authId: "ar_default",
+ });
+ expect(wrote).toBe(true);
+ expect((await store.authorizationAttemptFor(a))?.authId).toBe("ar_default");
+ });
+
+ test("a discard matches a row written with the column default", async () => {
+ const a = await actor("default-discard");
+ const row = await insertWithDefaultCreatedAt(a);
+ expect(
+ await store.discardAuthorizationAttempt({
+ actorId: a,
+ createdAt: row.createdAt,
+ }),
+ ).toBe(true);
+ expect(await store.authorizationAttemptFor(a)).toBeNull();
+ });
+
+ test("a created_at that has been through JSON still names its attempt", async () => {
+ const a = await actor("json-mint");
+ const got = await claim(a);
+ if (!got.claimed) throw new Error("claim failed");
+ // The declared type says `Date`; a serialised attempt hands back a string.
+ const serialised: { createdAt: Date } = JSON.parse(
+ JSON.stringify(got.attempt),
+ );
+ const wrote = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: serialised.createdAt,
+ authId: "ar_json",
+ });
+ expect(wrote).toBe(true);
+ expect((await store.authorizationAttemptFor(a))?.authId).toBe("ar_json");
+ });
+
+ test("a created_at that names no instant is refused, not matched", async () => {
+ const a = await actor("bad-instant");
+ await claim(a);
+ await expect(
+ store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: new Date("not a date"),
+ authId: "ar_bad",
+ }),
+ ).rejects.toThrow(/created_at/);
+ expect((await store.authorizationAttemptFor(a))?.authId).toBeNull();
+ });
+});
diff --git a/server/tests/arcade-auth-provider.test.ts b/server/tests/arcade-auth-provider.test.ts
new file mode 100644
index 000000000..8bb6d1aac
--- /dev/null
+++ b/server/tests/arcade-auth-provider.test.ts
@@ -0,0 +1,144 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import Arcade from "@arcadeai/arcadejs";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+
+/**
+ * `provider_id` IS THE AUTH PROVIDER AND NOT THE TOOLKIT, which is the whole of what this file
+ * exists to pin. One Google connection serves `Gmail` and `GoogleCalendar` both, and the live API
+ * refuses with an operator-named `"provider_id": "cpk-poc"` — a provider CONFIGURATION, not a
+ * toolkit. It coincides with the toolkit slug only when an operator names it that way, which
+ * `Github`, `Asana` and `Attio` all happen to do and which is every toolkit in this tree's
+ * fixtures. So the case that matters is constructed here rather than taken from the fixture: a
+ * toolkit whose provider is named DIFFERENTLY from it.
+ *
+ * Same prototype-stubbing pattern as `arcade-adapter.test.ts`, so nothing dials.
+ */
+type Methods = Record unknown>;
+const toolsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).tools,
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ on: Methods,
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+) {
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+});
+
+/** One listed action carrying the provider its toolkit's connections are held under. */
+const toolUnder = (toolkit: string, i: number, providerId: string | null) => ({
+ qualified_name: `${toolkit}.A${i}`,
+ toolkit: { name: toolkit },
+ requirements: {
+ authorization: {
+ id: `arcade-${i}`,
+ ...(providerId === null ? {} : { provider_id: providerId }),
+ oauth2: { scopes: [] },
+ },
+ },
+});
+
+describe("Arcade authProviderOf", () => {
+ test("answers the listing's provider for a toolkit named differently from it", async () => {
+ const asked: unknown[] = [];
+ stub(toolsProto(), "list", (query: unknown) => {
+ asked.push(query);
+ return Promise.resolve({
+ items: [
+ toolUnder("Gmail", 1, "google"),
+ toolUnder("Gmail", 2, "google"),
+ ],
+ });
+ });
+
+ const { broker } = createArcadeClient("never-dialled");
+
+ expect(await broker.authProviderOf("Gmail")).toBe("google");
+ // ONE page and no paging: every tool in a toolkit shares its auth provider.
+ expect(asked).toHaveLength(1);
+ expect((asked[0] as { toolkit?: string }).toolkit).toBe("Gmail");
+ });
+
+ test("carries the vendor's own case through untouched", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({ items: [toolUnder("Gmail", 1, "Google")] }),
+ );
+
+ expect(
+ await createArcadeClient("never-dialled").broker.authProviderOf("Gmail"),
+ ).toBe("Google");
+ });
+
+ test("answers null when the page names two distinct providers, never the first", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({
+ items: [
+ toolUnder("Mixed", 1, "google"),
+ toolUnder("Mixed", 2, "slack"),
+ ],
+ }),
+ );
+
+ expect(
+ await createArcadeClient("never-dialled").broker.authProviderOf("Mixed"),
+ ).toBeNull();
+ });
+
+ test("answers null — NEVER the toolkit name — when the listing does not say", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({ items: [toolUnder("Gmail", 1, null)] }),
+ );
+
+ expect(
+ await createArcadeClient("never-dialled").broker.authProviderOf("Gmail"),
+ ).toBeNull();
+ });
+
+ /**
+ * AN UNREADABLE AUTHORIZATION BESIDE A READABLE ONE IS UNANSWERED, NOT THE READABLE ONE'S ID.
+ * The seam's caller (`store.ts`'s sibling resolver) counts `null` as unanswered and ends nothing
+ * for it; answering `google` here would let it end rows on a listing it never read whole.
+ */
+ test("answers null when another action's authorization could not be read", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({
+ items: [
+ toolUnder("Gmail", 1, "google"),
+ {
+ qualified_name: "Gmail.A2",
+ requirements: { authorization: "oauth2" },
+ },
+ ],
+ }),
+ );
+
+ expect(
+ await createArcadeClient("never-dialled").broker.authProviderOf("Gmail"),
+ ).toBeNull();
+ });
+
+ test("still answers the provider when another action has NO authorization at all", async () => {
+ stub(toolsProto(), "list", () =>
+ Promise.resolve({
+ items: [
+ toolUnder("Gmail", 1, "google"),
+ { qualified_name: "Gmail.A2", requirements: null },
+ ],
+ }),
+ );
+
+ expect(
+ await createArcadeClient("never-dialled").broker.authProviderOf("Gmail"),
+ ).toBe("google");
+ });
+});
diff --git a/server/tests/arcade-authorize-cancel.test.ts b/server/tests/arcade-authorize-cancel.test.ts
new file mode 100644
index 000000000..ca2891fa1
--- /dev/null
+++ b/server/tests/arcade-authorize-cancel.test.ts
@@ -0,0 +1,655 @@
+// server/tests/arcade-authorize-cancel.test.ts
+import {
+ afterAll,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ test,
+} from "bun:test";
+import { randomUUID } from "node:crypto";
+import { eq, inArray, sql } from "drizzle-orm";
+import { Hono, type MiddlewareHandler } from "hono";
+import { createAuditStore } from "../src/audit";
+import type { AppVariables } from "../src/auth/guards";
+import { createDatabase } from "../src/db/client";
+import {
+ auditEvents,
+ brokeredConnections,
+ mcpServers,
+ mcpTools,
+ pendingAuthorizations,
+} from "../src/db/schema";
+import type { BrokerProvider } from "../src/plugins/access";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import { createPluginRoutes } from "../src/plugins/routes";
+import { createPluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+const personalApp = `arcade-cp-${suite}`;
+const sharedApp = `arcade-cs-${suite}`;
+const otherApp = `arcade-co-${suite}`;
+const movedApp = `arcade-cm-${suite}`;
+const composioApp = `composio-cp-${suite}`;
+const composioSharedApp = `composio-cs-${suite}`;
+const SHARED_VENDOR_ID = `openbot-deployment:acme-${suite}:R`;
+const ACTION = "Gmail.SendEmail";
+const SECRET_URL = "https://vendor.example/consent?secret=bearer-capability";
+
+let mints: { vendorUserId: string; action?: string; toolkit: string }[] = [];
+let nextAuthId: string | null = "ar_minted_1";
+
+const arcadeBroker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ authorizations: {
+ status: async () => {
+ throw new Error("nothing here polls an authorization");
+ },
+ confirmReturn: async () => {
+ throw new Error("nothing here confirms a return leg");
+ },
+ },
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async ({ account, toolkit, action }) => {
+ mints.push({ vendorUserId: account.vendorUserId, action, toolkit });
+ return { redirectUrl: SECRET_URL, authorizationId: nextAuthId };
+ },
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => "acme-bot",
+};
+
+/*
+ * A broker that runs NO per-action authorization flow — `authorizations: null`, the declared
+ * capability the cancel route's Composio half is reached under. `POST /authorize` refuses such a
+ * deployment with a 503 before any mint, so a mint reached under it is a defect, not a case.
+ */
+const composioBroker: ConnectedAppBroker = {
+ ...arcadeBroker,
+ grantIsLocal: true,
+ authorizations: null,
+ authorize: async () => {
+ throw new Error("nothing here mints under a broker with no flow");
+ },
+};
+
+function appAs(
+ role: "admin" | "user",
+ id: string,
+ provider: BrokerProvider = "arcade",
+ wrap: (
+ store: ReturnType,
+ ) => ReturnType = (s) => s,
+) {
+ const broker = provider === "arcade" ? arcadeBroker : composioBroker;
+ const baseStore = createPluginStore({
+ brokerProvider: provider,
+ database,
+ auditStore: createAuditStore(database),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }),
+ broker,
+ deploymentId: `acme-${suite}`,
+ });
+ const store = wrap(baseStore);
+ const signedIn: MiddlewareHandler<{ Variables: AppVariables }> = async (
+ context,
+ next,
+ ) => {
+ context.set("actor", { id, email: `${id}@example.test`, role } as never);
+ await next();
+ };
+ return new Hono().route(
+ "/api/plugins",
+ createPluginRoutes(
+ store,
+ signedIn,
+ async () => true,
+ {
+ publicUrl: "https://openbot.example",
+ appUrl: "https://app.example",
+ encryptionKey: `${"A".repeat(43)}=`,
+ personHasAccess: async () => true,
+ },
+ { broker, provider },
+ ),
+ );
+}
+
+/**
+ * An actor id for this suite, AND THE `users` ROW BEHIND IT.
+ *
+ * `pending_authorizations.actor_id` keys to `users` and cascades (`.user_id` must equal it), so the
+ * attempt these cases cancel cannot be minted for somebody who does not exist.
+ */
+const actors: string[] = [];
+const person = async (name: string) => {
+ const id = `${name}-${suite}`;
+ actors.push(id);
+ await seedPeople(database, [id]);
+ return id;
+};
+
+const authorize = (app: Hono, serverId: string) =>
+ app.request(`/api/plugins/servers/${serverId}/authorize`, {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ action: ACTION }),
+ });
+const cancel = (app: Hono, serverId: string) =>
+ app.request(`/api/plugins/servers/${serverId}/authorize`, {
+ method: "DELETE",
+ });
+
+const attemptsFor = (actorId: string) =>
+ database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, actorId));
+
+beforeAll(async () => {
+ await database.insert(mcpServers).values([
+ {
+ id: personalApp,
+ title: "Gmail",
+ vendor: "Arcade",
+ url: `arcade://P${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ },
+ {
+ id: sharedApp,
+ title: "Team Gmail",
+ vendor: "Arcade",
+ url: `arcade://S${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "shared",
+ sharedVendorUserId: SHARED_VENDOR_ID,
+ },
+ {
+ id: otherApp,
+ title: "Slack",
+ vendor: "Arcade",
+ url: `arcade://O${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ },
+ {
+ id: movedApp,
+ title: "Notion",
+ vendor: "Arcade",
+ url: `arcade://M${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ },
+ {
+ id: composioApp,
+ title: "Composio Gmail",
+ vendor: "Composio",
+ url: `composio://cp${suite}`,
+ provenance: "composio",
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ },
+ {
+ id: composioSharedApp,
+ title: "Composio Team Gmail",
+ vendor: "Composio",
+ url: `composio://cs${suite}`,
+ provenance: "composio",
+ authScheme: "OAUTH2",
+ accountMode: "shared",
+ sharedVendorUserId: SHARED_VENDOR_ID,
+ },
+ ]);
+ await database.insert(mcpTools).values(
+ [personalApp, sharedApp, otherApp, movedApp].map((serverId) => ({
+ serverId,
+ name: ACTION,
+ vendorName: ACTION,
+ })),
+ );
+});
+
+afterAll(async () => {
+ await database
+ .delete(mcpServers)
+ .where(
+ inArray(mcpServers.id, [
+ personalApp,
+ sharedApp,
+ otherApp,
+ movedApp,
+ composioApp,
+ composioSharedApp,
+ ]),
+ );
+ if (actors.length) {
+ await database
+ .delete(pendingAuthorizations)
+ .where(inArray(pendingAuthorizations.actorId, actors));
+ }
+ await removePeople(database, actors);
+ await database.$client.close();
+});
+
+beforeEach(() => {
+ mints = [];
+ nextAuthId = "ar_minted_1";
+});
+
+describe("DELETE /servers/:id/authorize", () => {
+ test("cancelling removes the attempt and a fresh authorization immediately succeeds", async () => {
+ const actor = await person("cancel");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ expect(await attemptsFor(actor)).toHaveLength(1);
+
+ const response = await cancel(app, personalApp);
+ expect(response.status).toBeLessThan(300);
+ expect(await attemptsFor(actor)).toEqual([]);
+
+ nextAuthId = "ar_minted_2";
+ const again = await authorize(app, personalApp);
+ expect(again.status).toBe(200);
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({
+ authId: "ar_minted_2",
+ completedAt: null,
+ outcome: null,
+ });
+ // created_at round-trips through a JS Date: it is what the cancel matches on.
+ const created = row?.createdAt as Date;
+ const [exact] = (await database.execute(
+ sql`select (created_at = ${created.toISOString()}::timestamptz) as exact
+ from pending_authorizations where actor_id = ${actor}`,
+ )) as unknown as { exact: boolean }[];
+ expect(exact?.exact).toBe(true);
+ });
+
+ test("cancelling an attempt whose mint never completed (no auth_id) removes it", async () => {
+ const actor = await person("noid");
+ nextAuthId = null;
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ const [before] = await attemptsFor(actor);
+ expect(before?.authId).toBeNull();
+ expect((await cancel(app, personalApp)).status).toBeLessThan(300);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("cancelling when no attempt exists is not an error", async () => {
+ const actor = await person("none");
+ const response = await cancel(appAs("user", actor), personalApp);
+ expect(response.status).toBeLessThan(300);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("a cancel naming one app does not cancel this actor's attempt on another", async () => {
+ const actor = await person("otherapp");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ const [before] = await attemptsFor(actor);
+ expect(before?.serverId).toBe(personalApp);
+
+ const response = await cancel(app, otherApp);
+ expect(response.status).toBeLessThan(300);
+ expect(await response.json()).toEqual({ cancelled: false });
+
+ const [after] = await attemptsFor(actor);
+ expect(after?.serverId).toBe(personalApp);
+ expect(after?.createdAt.getTime()).toBe(before?.createdAt.getTime());
+ expect(after?.authId).toBe("ar_minted_1");
+ });
+
+ test("a cancel issued for an OLD created_at does not remove a NEWER attempt by the same actor", async () => {
+ const actor = await person("stale");
+ const plain = appAs("user", actor);
+ expect((await authorize(plain, personalApp)).status).toBe(200);
+ const [old] = await attemptsFor(actor);
+ if (!old) throw new Error("no first attempt");
+ // The first attempt expires and a newer one replaces it, completing-in-flight with its own state.
+ await database.execute(
+ sql`update pending_authorizations set created_at = created_at - interval '16 minutes' where actor_id = ${actor}`,
+ );
+ const stale = {
+ ...old,
+ createdAt: new Date(old.createdAt.getTime() - 16 * 60 * 1000),
+ };
+ nextAuthId = "ar_newer";
+ expect((await authorize(plain, personalApp)).status).toBe(200);
+ const [newer] = await attemptsFor(actor);
+ expect(newer?.createdAt.getTime()).toBeGreaterThan(
+ stale.createdAt.getTime(),
+ );
+
+ // The cancel resolves the attempt it was issued for (the old one) and then loses the race.
+ const racing = appAs("user", actor, "arcade", (store) => ({
+ ...store,
+ authorizationAttemptFor: async () => stale,
+ }));
+ expect((await cancel(racing, personalApp)).status).toBeLessThan(300);
+
+ const [after] = await attemptsFor(actor);
+ expect(after?.createdAt.getTime()).toBe(
+ newer?.createdAt.getTime() as number,
+ );
+ expect(after?.authId).toBe("ar_newer");
+ expect(after?.completedAt).toBeNull();
+ expect(after?.outcome).toBeNull();
+ });
+
+ /*
+ * B2a. A COMPLETION THAT COMMITS BETWEEN THE CANCEL'S READ AND ITS DELETE. The route reads the
+ * live attempt, then discards it by `(actor_id, created_at)`; the read is wrapped here so the
+ * REAL completion runs, against the real row, in exactly that gap. Unguarded, the DELETE erased
+ * the completed attempt and the route answered `{ cancelled: true }` for a consent that had
+ * succeeded — the grant live at the vendor and the connection written.
+ */
+ test("a consent that completes under the cancel survives it, and the cancel says so", async () => {
+ const actor = await person("raced");
+ const toolkit = `race-${suite}`;
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 200,
+ );
+ const racing = appAs("user", actor, "arcade", (store) => ({
+ ...store,
+ authorizationAttemptFor: async (actorId: string) => {
+ const live = await store.authorizationAttemptFor(actorId);
+ if (live?.authId) {
+ await store.completeAuthorizationAttempt({
+ actorId,
+ authId: live.authId,
+ toolkit,
+ account: {
+ holder: "person",
+ userId: actorId,
+ vendorUserId: live.vendorUserId,
+ },
+ action: ACTION,
+ scopes: [],
+ authProvider: "google",
+ });
+ }
+ return live;
+ },
+ }));
+ try {
+ const response = await cancel(racing, personalApp);
+ // One comparison, so a failure shows the answer AND what became of the record side by side.
+ expect({
+ status: response.status,
+ body: await response.json(),
+ outcomes: (await attemptsFor(actor)).map((row) => row.outcome),
+ }).toEqual({
+ status: 409,
+ body: {
+ error:
+ "That authorization finished before it could be cancelled, so the account is connected. Disconnect it if you did not mean to connect it.",
+ },
+ outcomes: ["completed"],
+ });
+ } finally {
+ await database
+ .delete(brokeredConnections)
+ .where(eq(brokeredConnections.app, toolkit));
+ }
+ });
+
+ /*
+ * B5. CANCELLING IS AN OPERATION ON THE ATTEMPT, AND THE ATTEMPT OUTLIVES WHAT THE APP LOOKS
+ * LIKE. The route used to resolve the app FIRST and refuse on anything `brokeredAccountFor`
+ * refuses — while reading nothing off its success branch. So every way an app can stop
+ * resolving, with a live attempt still standing, was a person holding a pending authorization
+ * they could not retract: the attempt key is the actor ALONE, so that one row also refused them
+ * a new authorization on ANY app for the full fifteen minutes.
+ *
+ * A HARD `DELETE` OF THE SERVER ROW IS NOT ONE OF THOSE WAYS, and that is worth writing down
+ * rather than testing: `pending_authorizations.server_id` is `references(mcpServers.id,
+ * { onDelete: "cascade" })`, so `removeServer` takes the attempt with it and there is nothing
+ * left to cancel. The two below are reachable, and both by an administrator doing something
+ * ordinary.
+ */
+ test("an attempt on an app whose url no longer parses as brokered can still be cancelled", async () => {
+ const actor = await person("moved");
+ const app = appAs("user", actor);
+ expect((await authorize(app, movedApp)).status).toBe(200);
+ expect(await attemptsFor(actor)).toHaveLength(1);
+
+ // The app is re-pointed at an ordinary MCP endpoint: `appOfUrl` now answers null for it.
+ await database
+ .update(mcpServers)
+ .set({ url: `https://moved-${suite}.example/mcp` })
+ .where(eq(mcpServers.id, movedApp));
+ try {
+ const response = await cancel(app, movedApp);
+ expect(response.status).toBeLessThan(300);
+ expect(await response.json()).toEqual({ cancelled: true });
+ expect(await attemptsFor(actor)).toEqual([]);
+ } finally {
+ await database
+ .update(mcpServers)
+ .set({ url: `arcade://M${suite}` })
+ .where(eq(mcpServers.id, movedApp));
+ }
+ });
+
+ test("an administrator demoted mid-consent can still cancel their own Shared-app attempt", async () => {
+ const actor = await person("demoted");
+ expect((await authorize(appAs("admin", actor), sharedApp)).status).toBe(
+ 200,
+ );
+ const [before] = await attemptsFor(actor);
+ expect(before?.serverId).toBe(sharedApp);
+ expect(before?.vendorUserId).toBe(SHARED_VENDOR_ID);
+
+ // The same person, no longer an administrator, with their own consent still in flight.
+ const response = await cancel(appAs("user", actor), sharedApp);
+ expect(response.status).toBeLessThan(300);
+ expect(await response.json()).toEqual({ cancelled: true });
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ /*
+ * AND THE REFUSALS ARE NOT TRADED AWAY FOR THAT. With nothing of this actor's to cancel, a
+ * DELETE aimed at a row that is not brokered is still told so, rather than flattened into a
+ * `{ cancelled: false }` that claims the request was understood.
+ */
+ test("with no attempt of this actor's, a non-brokered app is still refused in so many words", async () => {
+ const actor = await person("refused");
+ const app = appAs("user", actor);
+ await database
+ .update(mcpServers)
+ .set({ url: `https://refused-${suite}.example/mcp` })
+ .where(eq(mcpServers.id, movedApp));
+ try {
+ const response = await cancel(app, movedApp);
+ expect(response.status).toBe(400);
+ expect(await response.json()).toEqual({
+ error: "That app is not reached through a broker.",
+ });
+ } finally {
+ await database
+ .update(mcpServers)
+ .set({ url: `arcade://M${suite}` })
+ .where(eq(mcpServers.id, movedApp));
+ }
+ });
+});
+
+/*
+ * B5a. THE COMPOSIO HALF: CANCEL UNDER A BROKER WHOSE `authorizations` IS NULL.
+ *
+ * The cancel route reads no flow capability at all — it resolves the attempt row first and
+ * discards it, and only with nothing of this actor's on the named app does it resolve the app
+ * through `brokeredAccountFor`, under the ACTIVE provider's url parser. So on such a deployment
+ * the route is still reachable, and these pin what it answers there: a cancel is not the POST's
+ * "this deployment does not authorize per action" 503, an attempt left standing across a switch
+ * of provider is still the person's to retract, and the refusals keep their words under the
+ * Composio parser.
+ */
+describe("DELETE /servers/:id/authorize on a deployment whose broker runs no authorization flow", () => {
+ test("with nothing standing, a Composio app answers { cancelled: false }, not the POST's 503", async () => {
+ const actor = await person("c-none");
+ const response = await cancel(
+ appAs("user", actor, "composio"),
+ composioApp,
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({ cancelled: false });
+ expect(mints).toEqual([]);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("an attempt minted before the deployment switched to Composio can still be cancelled", async () => {
+ const actor = await person("c-switched");
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 200,
+ );
+ const [before] = await attemptsFor(actor);
+ expect(before?.serverId).toBe(personalApp);
+ mints = [];
+
+ // Under Composio `arcade://` parses to no app at all, so only the attempt-first order reaches it.
+ const response = await cancel(
+ appAs("user", actor, "composio"),
+ personalApp,
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({ cancelled: true });
+ expect(await attemptsFor(actor)).toEqual([]);
+ expect(mints).toEqual([]);
+ });
+
+ test("a cancel naming a Composio app does not cancel this actor's attempt on another app", async () => {
+ const actor = await person("c-other");
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 200,
+ );
+ const [before] = await attemptsFor(actor);
+
+ const response = await cancel(
+ appAs("user", actor, "composio"),
+ composioApp,
+ );
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({ cancelled: false });
+
+ const [after] = await attemptsFor(actor);
+ expect(after?.serverId).toBe(personalApp);
+ expect(after?.createdAt.getTime()).toBe(before?.createdAt.getTime());
+ expect(after?.authId).toBe("ar_minted_1");
+ });
+
+ test("with nothing standing, an `arcade://` row is refused under the Composio parser in so many words", async () => {
+ const actor = await person("c-refused");
+ const response = await cancel(
+ appAs("user", actor, "composio"),
+ personalApp,
+ );
+ expect(response.status).toBe(400);
+ expect(await response.json()).toEqual({
+ error: "That app is not reached through a broker.",
+ });
+ });
+
+ test("with nothing standing, a non-administrator aimed at a Shared Composio app is refused", async () => {
+ const actor = await person("c-shared");
+ const response = await cancel(
+ appAs("user", actor, "composio"),
+ composioSharedApp,
+ );
+ expect(response.status).toBe(403);
+ expect(await response.json()).toEqual({
+ error: "An administrator connects shared apps.",
+ });
+ });
+});
+
+/*
+ * B5b. A RETRACTION IS ON THE TRAIL. The mint/claim/complete lifecycle exists to make a person's
+ * consent attributable, and `mcp.account_connected` records the one ending where they said yes. A
+ * person pressing Cancel on their own pending authorization is the other deliberate ending, and it
+ * used to leave nothing at all: the row was deleted and no record said who withdrew what.
+ *
+ * Read by the ACTOR in the payload, which is where every writer on this lifecycle puts who did it,
+ * so another test's rows on the same app can neither satisfy nor spoil these.
+ */
+describe("DELETE /servers/:id/authorize — the audit row", () => {
+ const cancellationsBy = (actorId: string) =>
+ database
+ .select()
+ .from(auditEvents)
+ .where(
+ sql`${auditEvents.eventType} = 'mcp.authorization_cancelled' and ${auditEvents.payload}->>'actor' = ${actorId}`,
+ );
+
+ test("cancelling a live attempt files one mcp.authorization_cancelled row against the app", async () => {
+ const actor = await person("audited");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+
+ const response = await cancel(app, personalApp);
+ expect(await response.json()).toEqual({ cancelled: true });
+
+ const rows = await cancellationsBy(actor);
+ expect(rows).toHaveLength(1);
+ const [row] = rows;
+ // The app's slug, which is what `mcp.account_connected` and `mcp.account_disconnected` file a
+ // brokered app under — so one query on the target answers every ending of one app's consent.
+ expect(row?.targetType).toBe("mcp_server");
+ expect(row?.targetId).toBe(`P${suite}`);
+ expect(row?.initiatorKind).toBe("person");
+ expect(row?.payload).toEqual({
+ actor,
+ server: `P${suite}`,
+ serverId: personalApp,
+ action: ACTION,
+ holder: "person",
+ minted: true,
+ });
+ // THE CONFIDENTIALITY PROMISE: neither the bearer consent url nor the vendor's handle for the
+ // flow reaches the trail.
+ const written = JSON.stringify(row);
+ expect(written).not.toContain(SECRET_URL);
+ expect(written).not.toContain("ar_minted_1");
+ });
+
+ test("a cancel that loses the race to a newer attempt removes nothing and files no row", async () => {
+ const actor = await person("unaudited");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ const [old] = await attemptsFor(actor);
+ if (!old) throw new Error("no first attempt");
+ // The cancel resolved an attempt that a newer one has since replaced: its delete matches nothing.
+ const stale = {
+ ...old,
+ createdAt: new Date(old.createdAt.getTime() - 16 * 60 * 1000),
+ };
+ const racing = appAs("user", actor, "arcade", (store) => ({
+ ...store,
+ authorizationAttemptFor: async () => stale,
+ }));
+ expect(await (await cancel(racing, personalApp)).json()).toEqual({
+ cancelled: false,
+ });
+ expect(await attemptsFor(actor)).toHaveLength(1);
+ expect(await cancellationsBy(actor)).toEqual([]);
+ });
+});
diff --git a/server/tests/arcade-authorize-route.test.ts b/server/tests/arcade-authorize-route.test.ts
new file mode 100644
index 000000000..755c205f8
--- /dev/null
+++ b/server/tests/arcade-authorize-route.test.ts
@@ -0,0 +1,808 @@
+// server/tests/arcade-authorize-route.test.ts
+import {
+ afterAll,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ test,
+} from "bun:test";
+import { APIConnectionError, APIError } from "@arcadeai/arcadejs";
+import { randomUUID } from "node:crypto";
+import { eq, inArray, sql } from "drizzle-orm";
+import { Hono, type MiddlewareHandler } from "hono";
+import { createAuditStore } from "../src/audit";
+import type { AppVariables } from "../src/auth/guards";
+import { createDatabase } from "../src/db/client";
+import { mcpServers, mcpTools, pendingAuthorizations } from "../src/db/schema";
+import type { BrokerProvider } from "../src/plugins/access";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import { createPluginRoutes } from "../src/plugins/routes";
+import { createPluginStore, type PluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+const personalApp = `arcade-p-${suite}`;
+const sharedApp = `arcade-s-${suite}`;
+const composioApp = `composio-p-${suite}`;
+const SHARED_VENDOR_ID = `openbot-deployment:acme-${suite}:R`;
+const ACTION = "Gmail.SendEmail";
+const SECRET_URL = "https://vendor.example/consent?secret=bearer-capability";
+
+let mints: { vendorUserId: string; action?: string; toolkit: string }[] = [];
+let nextAuthId: string | null = "ar_minted_1";
+// What the next mint does before answering; a test sets it to throw, or to hold the mint open.
+let beforeMint: (() => Promise) | null = null;
+
+const arcadeBroker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ authorizations: {
+ status: async () => {
+ throw new Error("nothing here polls an authorization");
+ },
+ confirmReturn: async () => {
+ throw new Error("nothing here confirms a return leg");
+ },
+ },
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async ({ account, toolkit, action }) => {
+ mints.push({ vendorUserId: account.vendorUserId, action, toolkit });
+ await beforeMint?.();
+ return { redirectUrl: SECRET_URL, authorizationId: nextAuthId };
+ },
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => "acme-bot",
+};
+
+const composioBroker: ConnectedAppBroker = {
+ ...arcadeBroker,
+ grantIsLocal: true,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ authorizations: null,
+ authorize: async ({ account, toolkit, action }) => {
+ mints.push({ vendorUserId: account.vendorUserId, action, toolkit });
+ return { redirectUrl: SECRET_URL, authorizationId: null };
+ },
+};
+
+function appAs(
+ role: "admin" | "user",
+ id: string,
+ provider: BrokerProvider = "arcade",
+ // What this app's store does differently. One test makes the local write after a SUCCESSFUL mint
+ // fail, which is the only way to reach the route's record-after-the-fact branch.
+ override: Partial = {},
+) {
+ const broker = provider === "arcade" ? arcadeBroker : composioBroker;
+ const store = createPluginStore({
+ brokerProvider: provider,
+ database,
+ auditStore: createAuditStore(database),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }),
+ broker,
+ deploymentId: `acme-${suite}`,
+ });
+ const signedIn: MiddlewareHandler<{ Variables: AppVariables }> = async (
+ context,
+ next,
+ ) => {
+ context.set("actor", { id, email: `${id}@example.test`, role } as never);
+ await next();
+ };
+ return new Hono().route(
+ "/api/plugins",
+ createPluginRoutes(
+ { ...store, ...override },
+ signedIn,
+ async () => true,
+ {
+ publicUrl: "https://openbot.example",
+ appUrl: "https://app.example",
+ encryptionKey: `${"A".repeat(43)}=`,
+ personHasAccess: async () => true,
+ },
+ { broker, provider },
+ ),
+ );
+}
+
+/**
+ * An actor id for this suite, AND THE `users` ROW BEHIND IT.
+ *
+ * `pending_authorizations.actor_id` keys to `users` and cascades (`.user_id` must equal it), so the
+ * attempt a mint writes cannot name somebody who does not exist.
+ */
+const actors: string[] = [];
+const person = async (name: string) => {
+ const id = `${name}-${suite}`;
+ actors.push(id);
+ await seedPeople(database, [id]);
+ return id;
+};
+
+const authorize = (
+ app: Hono,
+ serverId: string,
+ body: unknown = { action: ACTION },
+) =>
+ app.request(`/api/plugins/servers/${serverId}/authorize`, {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(body),
+ });
+
+/**
+ * Every line the deployment wrote while `work` ran, each as the text it would have printed. The
+ * arguments are joined rather than mapped through one `String()`, so an object logged beside a
+ * string still shows up as `[object Object]`, which is the shape a leak hides in.
+ */
+async function heard(work: () => T | Promise): Promise<[T, string[]]> {
+ const said: string[] = [];
+ const before = globalThis.console;
+ const hear = (...args: unknown[]) =>
+ said.push(args.map((arg) => String(arg)).join(" "));
+ globalThis.console = {
+ ...before,
+ log: hear,
+ info: hear,
+ warn: hear,
+ error: hear,
+ debug: hear,
+ } as typeof console;
+ try {
+ return [await work(), said];
+ } finally {
+ globalThis.console = before;
+ }
+}
+
+const attemptsFor = (actorId: string) =>
+ database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, actorId));
+
+beforeAll(async () => {
+ await database.insert(mcpServers).values([
+ {
+ id: personalApp,
+ title: "Gmail",
+ vendor: "Arcade",
+ url: `arcade://P${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ },
+ {
+ id: sharedApp,
+ title: "Team Gmail",
+ vendor: "Arcade",
+ url: `arcade://S${suite}`,
+ provenance: "arcade",
+ authScheme: "OAUTH2",
+ accountMode: "shared",
+ sharedVendorUserId: SHARED_VENDOR_ID,
+ },
+ {
+ id: composioApp,
+ title: "Composio Gmail",
+ vendor: "Composio",
+ url: `composio://c${suite}`,
+ provenance: "composio",
+ authScheme: "OAUTH2",
+ },
+ ]);
+ await database.insert(mcpTools).values(
+ [personalApp, sharedApp, composioApp].map((serverId) => ({
+ serverId,
+ name: ACTION,
+ vendorName: ACTION,
+ })),
+ );
+});
+
+afterAll(async () => {
+ await database
+ .delete(mcpServers)
+ .where(inArray(mcpServers.id, [personalApp, sharedApp, composioApp]));
+ if (actors.length) {
+ await database
+ .delete(pendingAuthorizations)
+ .where(inArray(pendingAuthorizations.actorId, actors));
+ }
+ await removePeople(database, actors);
+ await database.$client.close();
+});
+
+beforeEach(() => {
+ mints = [];
+ nextAuthId = "ar_minted_1";
+ beforeMint = null;
+});
+
+describe("POST /servers/:id/authorize", () => {
+ test("mints for the named action and the row records the exact account and the authorization id", async () => {
+ const actor = await person("mint");
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({ authorizationUrl: SECRET_URL });
+ expect(mints).toEqual([
+ { vendorUserId: actor, action: ACTION, toolkit: `P${suite}` },
+ ]);
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({
+ actorId: actor,
+ serverId: personalApp,
+ action: ACTION,
+ holder: "person",
+ userId: actor,
+ vendorUserId: actor,
+ authId: "ar_minted_1",
+ completedAt: null,
+ outcome: null,
+ });
+ // created_at round-trips through a JS Date: the mint matched on it, and the stored value is
+ // exactly millisecond-precise.
+ const created = row?.createdAt as Date;
+ expect(new Date(created.getTime()).getTime()).toBe(created.getTime());
+ const [exact] = (await database.execute(
+ sql`select (created_at = date_trunc('milliseconds', created_at)) as exact
+ from pending_authorizations where actor_id = ${actor}`,
+ )) as unknown as { exact: boolean }[];
+ expect(exact?.exact).toBe(true);
+ });
+
+ test("a null authorization id is recorded as NULL, never collapsed", async () => {
+ const actor = await person("nullid");
+ nextAuthId = null;
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(200);
+ const [row] = await attemptsFor(actor);
+ expect(row?.authId).toBeNull();
+ expect(row?.completedAt).toBeNull();
+ });
+
+ test("an action the app does not publish is a 400 with no attempt and no mint", async () => {
+ const actor = await person("unpublished");
+ const response = await authorize(appAs("user", actor), personalApp, {
+ action: "Gmail.DeleteEverything",
+ });
+ expect(response.status).toBe(400);
+ expect(mints).toEqual([]);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("a differently-cased action is not accepted", async () => {
+ const actor = await person("cased");
+ const response = await authorize(appAs("user", actor), personalApp, {
+ action: ACTION.toLowerCase(),
+ });
+ expect(response.status).toBe(400);
+ expect(mints).toEqual([]);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("a non-admin on a Shared app is a 403 with no attempt", async () => {
+ const actor = await person("shared-user");
+ const response = await authorize(appAs("user", actor), sharedApp);
+ expect(response.status).toBe(403);
+ expect(mints).toEqual([]);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("an admin on a Shared app records the deployment's account on the row", async () => {
+ const actor = await person("shared-admin");
+ const response = await authorize(appAs("admin", actor), sharedApp);
+ expect(response.status).toBe(200);
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({
+ holder: "deployment",
+ userId: null,
+ vendorUserId: SHARED_VENDOR_ID,
+ authId: "ar_minted_1",
+ });
+ });
+
+ test("a second authorization while one is live is a 409 naming the first", async () => {
+ const actor = await person("live");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ const [first] = await attemptsFor(actor);
+ mints = [];
+ const second = await authorize(app, personalApp);
+ expect(second.status).toBe(409);
+ expect(JSON.stringify(await second.json())).toContain(ACTION);
+ expect(mints).toEqual([]);
+ const [after] = await attemptsFor(actor);
+ expect(after?.createdAt.getTime()).toBe(
+ first?.createdAt.getTime() as number,
+ );
+ expect(after?.authId).toBe("ar_minted_1");
+ });
+
+ test("an authorization after the first expired succeeds, replacing it", async () => {
+ const actor = await person("expired");
+ const app = appAs("user", actor);
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ await database.execute(
+ sql`update pending_authorizations set created_at = created_at - interval '16 minutes' where actor_id = ${actor}`,
+ );
+ const [old] = await attemptsFor(actor);
+ nextAuthId = "ar_minted_2";
+ expect((await authorize(app, personalApp)).status).toBe(200);
+ const rows = await attemptsFor(actor);
+ expect(rows).toHaveLength(1);
+ expect(rows[0]?.authId).toBe("ar_minted_2");
+ expect(rows[0]?.createdAt.getTime()).toBeGreaterThan(
+ old?.createdAt.getTime() as number,
+ );
+ });
+
+ test("the authorization url appears in no audit row", async () => {
+ const actor = await person("audit");
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 200,
+ );
+ const leaked = await database.execute(
+ sql`select id from audit_events
+ where payload::text like ${"%bearer-capability%"}
+ or target_id like ${"%bearer-capability%"}`,
+ );
+ expect(leaked.length).toBe(0);
+ });
+
+ test("a deployment whose broker runs no authorization flow answers 503 and writes no attempt", async () => {
+ const actor = await person("composio");
+ const response = await authorize(
+ appAs("user", actor, "composio"),
+ composioApp,
+ );
+ expect(response.status).toBe(503);
+ expect(mints).toEqual([]);
+ expect(await attemptsFor(actor)).toEqual([]);
+ expect(JSON.stringify(await response.json())).not.toContain(
+ "vendor.example",
+ );
+ });
+
+ test("the gate answers before whose account it is: a non-admin on a Shared app of such a deployment is a 503, not a 403", async () => {
+ const actor = await person("composio-shared");
+ const response = await authorize(
+ appAs("user", actor, "composio"),
+ sharedApp,
+ );
+ expect(response.status).toBe(503);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ describe("a mint that fails", () => {
+ const refuse = async () => {
+ throw new APIError(
+ 500,
+ { name: "boom", message: "vendor-body-secret" },
+ "500",
+ new Headers(),
+ );
+ };
+
+ test("a definite vendor refusal deletes the attempt, and a late cleanup can never delete a newer one", async () => {
+ const actor = await person("definite");
+ beforeMint = refuse;
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(502);
+ expect(JSON.stringify(await response.json())).not.toContain(
+ "vendor-body-secret",
+ );
+ expect(await attemptsFor(actor)).toEqual([]);
+
+ // The actor starts again; the first attempt's cleanup then arrives late.
+ beforeMint = null;
+ nextAuthId = "ar_newer";
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 200,
+ );
+ const [newer] = await attemptsFor(actor);
+ const store = createPluginStore({
+ brokerProvider: "arcade",
+ database,
+ auditStore: createAuditStore(database),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }),
+ broker: arcadeBroker,
+ deploymentId: `acme-${suite}`,
+ });
+ const staleCreatedAt = new Date(
+ (newer?.createdAt.getTime() as number) - 5,
+ );
+ expect(
+ await store.discardAuthorizationAttempt({
+ actorId: actor,
+ createdAt: staleCreatedAt,
+ }),
+ ).toBe(false);
+ const [survivor] = await attemptsFor(actor);
+ expect(survivor).toMatchObject({
+ authId: "ar_newer",
+ completedAt: null,
+ outcome: null,
+ });
+ expect(survivor?.createdAt.getTime()).toBe(newer?.createdAt.getTime());
+ });
+
+ test("a definite refusal whose clean-up also fails still answers the refusal, not a bodyless 500", async () => {
+ const actor = await person("definite-stuck");
+ beforeMint = refuse;
+ const response = await authorize(
+ appAs("user", actor, "arcade", {
+ discardAuthorizationAttempt: async () => {
+ throw new Error("Failed query: delete from pending_authorizations");
+ },
+ }),
+ personalApp,
+ );
+ // The refusal the handler worked out, not the generic 500 a thrown clean-up produces.
+ expect(response.status).toBe(502);
+ const body = (await response.json()) as { error?: string };
+ expect(typeof body.error).toBe("string");
+ expect(JSON.stringify(body)).not.toContain("vendor-body-secret");
+ expect(JSON.stringify(body)).not.toContain("Failed query");
+ // Nothing cleared it, so the row stands and expires on its own.
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({ authId: null, completedAt: null });
+ });
+
+ test("an authored refusal is definite: nothing reached the vendor, so the attempt is deleted", async () => {
+ const actor = await person("authored");
+ const { BrokerRefusalError } = await import("../src/plugins/broker");
+ beforeMint = async () => {
+ throw new BrokerRefusalError("Arcade needs an action.");
+ };
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(503);
+ expect(await attemptsFor(actor)).toEqual([]);
+ });
+
+ test("an uncertain failure (status undefined) leaves the attempt standing with a null auth_id", async () => {
+ const actor = await person("uncertain");
+ beforeMint = async () => {
+ throw new APIConnectionError({ message: "socket dropped" });
+ };
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(502);
+ const body = JSON.stringify(await response.json());
+ expect(body).not.toContain("bearer-capability");
+ const rows = await attemptsFor(actor);
+ expect(rows).toHaveLength(1);
+ expect(rows[0]).toMatchObject({
+ authId: null,
+ completedAt: null,
+ outcome: null,
+ });
+ const created = rows[0]?.createdAt as Date;
+ expect(new Date(created.getTime()).getTime()).toBe(created.getTime());
+ // ...and it still holds the actor: a second start is a 409, not a second flow.
+ beforeMint = null;
+ expect((await authorize(appAs("user", actor), personalApp)).status).toBe(
+ 409,
+ );
+ });
+
+ /*
+ * L4-a: THE UNCERTAIN BRANCH LEAVES THE CLAIM STANDING, AND IT USED TO LEAVE NOTHING ELSE.
+ *
+ * The outcome assertions here already passed before the fix: the row stands with a NULL
+ * `auth_id` and holds the actor. What was missing is the trace. A flow the vendor may have
+ * started went unrecorded everywhere, so nobody who could end it at the vendor could find it.
+ */
+ test("an uncertain mint leaves the claim standing AND leaves an operator a line naming it", async () => {
+ const actor = await person("uncertain-line");
+ beforeMint = async () => {
+ throw new APIConnectionError({ message: "socket dropped" });
+ };
+ const [response, said] = await heard(() =>
+ authorize(appAs("user", actor), personalApp),
+ );
+ expect(response.status).toBe(502);
+
+ // NOT ENDED ON A GUESS: the claim stands, with no id, holding the actor.
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({
+ authId: null,
+ completedAt: null,
+ outcome: null,
+ });
+
+ // AND SAID OUT LOUD, with what an operator needs to find the attempt and the flow.
+ const line = said.find((text) =>
+ text.includes("arcade-mint-outcome-unknown"),
+ );
+ expect(line).toBeDefined();
+ const logged = JSON.parse(line ?? "{}") as Record;
+ expect(logged.server).toBe(personalApp);
+ expect(logged.action).toBe(ACTION);
+ expect(logged.attemptStartedAt).toBe(row?.createdAt.toISOString());
+ expect(logged.error).toBe("socket dropped");
+ // Nothing that is not ours: no bearer url, no vendor user id, no authorization id field.
+ expect(line).not.toContain("bearer-capability");
+ expect(line).not.toContain(actor);
+ expect(Object.keys(logged)).not.toContain("authorizationId");
+
+ /*
+ * L4a-b: AND THE SENTENCE THE PERSON GETS NAMES ONLY STEPS THIS BRANCH CAN DELIVER.
+ *
+ * It used to end "finish it if you were sent to a consent screen", while the line asserted
+ * just above says of the same branch that nobody was sent to one — and the line is the one
+ * that is true, because reaching here means the mint never answered a url. Somebody acting on
+ * the old sentence searched their tabs for a consent screen that was never opened.
+ *
+ * ASSERTED AS THE ABSENCE OF THE INSTRUCTION, not as the presence of a replacement: the words
+ * may be reworded, and what must not come back is a step nobody can take. The bounded wait is
+ * checked too, because it is the one thing here that IS actionable.
+ */
+ const { error: said502 } = (await response.json()) as { error: string };
+ expect(said502).not.toContain("consent screen, before starting another");
+ expect(said502).toContain("nobody was sent to a consent screen");
+ expect(said502).toContain("expires within fifteen minutes");
+ });
+
+ test("the missing-id mint is uncertain and leaves the same line, carrying the adapter's sentence", async () => {
+ const actor = await person("noid-line");
+ beforeMint = async () => {
+ throw new Error("Arcade answered an authorization with no id.");
+ };
+ const [response, said] = await heard(() =>
+ authorize(appAs("user", actor), personalApp),
+ );
+ expect(response.status).toBe(502);
+ const line = said.find((text) =>
+ text.includes("arcade-mint-outcome-unknown"),
+ );
+ expect(line).toBeDefined();
+ expect(line).toContain("Arcade answered an authorization with no id.");
+ expect(line).not.toContain("bearer-capability");
+ });
+
+ test("a definite refusal is not an unknown outcome, so it leaves no such line", async () => {
+ const actor = await person("definite-no-line");
+ beforeMint = refuse;
+ const [response, said] = await heard(() =>
+ authorize(appAs("user", actor), personalApp),
+ );
+ expect(response.status).toBe(502);
+ expect(await attemptsFor(actor)).toEqual([]);
+ expect(
+ said.filter((text) => text.includes("arcade-mint-outcome-unknown")),
+ ).toEqual([]);
+ });
+
+ test("a non-SDK throw (the adapter's missing-id Error) is uncertain too: the attempt stands", async () => {
+ const actor = await person("noid");
+ beforeMint = async () => {
+ throw new Error("Arcade answered an authorization with no id.");
+ };
+ const response = await authorize(appAs("user", actor), personalApp);
+ expect(response.status).toBe(502);
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({
+ authId: null,
+ completedAt: null,
+ outcome: null,
+ });
+ });
+ });
+
+ describe("a mint that succeeded and could not be recorded", () => {
+ // The local UPDATE after the vendor said yes. `DrizzleQueryError`'s own message is the
+ // statement and its bound values, which is the shape this route must not pass on.
+ const failWrite = {
+ recordAuthorizationMint: async () => {
+ throw new Error(
+ 'Failed query: update "pending_authorizations" set "auth_id" = $1 -- params: ["ar_minted_1"]',
+ );
+ },
+ } as Partial;
+
+ test("the person is told, is not sent to the consent screen, and the url does not travel", async () => {
+ const actor = await person("unrecorded");
+ const response = await authorize(
+ appAs("user", actor, "arcade", failWrite),
+ personalApp,
+ );
+ // Not a bodyless 500: a sentence, and nothing of the vendor's answer in it.
+ expect(response.status).toBe(500);
+ const body = (await response.json()) as {
+ error?: string;
+ authorizationUrl?: string;
+ };
+ expect(typeof body.error).toBe("string");
+ expect(body.authorizationUrl).toBeUndefined();
+ expect(JSON.stringify(body)).not.toContain("bearer-capability");
+ expect(JSON.stringify(body)).not.toContain("Failed query");
+ // The mint really did happen at the vendor.
+ expect(mints).toHaveLength(1);
+ });
+
+ test("the actor is not blocked: the dead attempt is cleared and a second start mints again", async () => {
+ const actor = await person("unrecorded-retry");
+ expect(
+ (
+ await authorize(
+ appAs("user", actor, "arcade", failWrite),
+ personalApp,
+ )
+ ).status,
+ ).toBe(500);
+ // A row holding a null auth_id can never complete and would hold the actor for the TTL.
+ expect(await attemptsFor(actor)).toEqual([]);
+ mints = [];
+ nextAuthId = "ar_after_retry";
+ const second = await authorize(appAs("user", actor), personalApp);
+ expect(second.status).toBe(200);
+ expect(mints).toHaveLength(1);
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({ authId: "ar_after_retry", outcome: null });
+ });
+
+ test("a failed clean-up still answers a sentence rather than throwing past the handler", async () => {
+ const actor = await person("unrecorded-stuck");
+ const response = await authorize(
+ appAs("user", actor, "arcade", {
+ ...failWrite,
+ discardAuthorizationAttempt: async () => {
+ throw new Error("Failed query: delete from pending_authorizations");
+ },
+ }),
+ personalApp,
+ );
+ expect(response.status).toBe(500);
+ const body = (await response.json()) as { error?: string };
+ expect(typeof body.error).toBe("string");
+ expect(JSON.stringify(body)).not.toContain("Failed query");
+ // Nothing cleared it, so the row stands and expires on its own.
+ const [row] = await attemptsFor(actor);
+ expect(row).toMatchObject({ authId: null, completedAt: null });
+ });
+ });
+
+ test("two concurrent starts by one actor yield exactly one 200 and one 409", async () => {
+ const actor = await person("race");
+ // Hold the winner's mint open so the loser's claim runs while it is in flight.
+ let release: () => void = () => {};
+ const held = new Promise((resolve) => {
+ release = resolve;
+ });
+ beforeMint = () => held;
+ const app = appAs("user", actor);
+ const first = authorize(app, personalApp);
+ const second = authorize(app, personalApp);
+ const settled = Promise.all([first, second]);
+ setTimeout(release, 150);
+ const responses = await settled;
+ expect(responses.map((r) => r.status).sort()).toEqual([200, 409]);
+ expect(mints).toHaveLength(1);
+ const rows = await attemptsFor(actor);
+ expect(rows).toHaveLength(1);
+ expect(rows[0]).toMatchObject({
+ authId: "ar_minted_1",
+ completedAt: null,
+ outcome: null,
+ });
+ });
+
+ test("a Shared app is authorized under the SESSION SUBJECT, not the deployment's credential account", async () => {
+ const actor = await person("team-bot");
+ expect((await authorize(appAs("admin", actor), sharedApp)).status).toBe(
+ 200,
+ );
+ const rows = await attemptsFor(actor);
+ expect(rows).toHaveLength(1);
+ expect(rows[0]).toMatchObject({
+ actorId: actor,
+ holder: "deployment",
+ vendorUserId: SHARED_VENDOR_ID,
+ completedAt: null,
+ outcome: null,
+ });
+ expect(rows[0]?.actorId).not.toBe(SHARED_VENDOR_ID);
+ const stray = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, SHARED_VENDOR_ID));
+ expect(stray).toEqual([]);
+ });
+
+ describe("a mint that the store declines to record", () => {
+ const cancel = (app: Hono, serverId: string) =>
+ app.request(`/api/plugins/servers/${serverId}/authorize`, {
+ method: "DELETE",
+ });
+
+ /*
+ * B5d. The mint is held open at the vendor, the person cancels, and only then does the vendor
+ * answer. `recordAuthorizationMint` finds no attempt under `(actor_id, created_at)` and writes
+ * nothing. The consent url the vendor answered is a bearer capability for a flow that this
+ * deployment no longer has any record of, so it must not be handed out.
+ */
+ test("a cancel that lands while the mint is in flight: no consent url, no attempt, a 409 that says why", async () => {
+ const actor = await person("cancel-mid-mint");
+ let release: () => void = () => {};
+ const held = new Promise((resolve) => {
+ release = resolve;
+ });
+ let entered: () => void = () => {};
+ const minting = new Promise((resolve) => {
+ entered = resolve;
+ });
+ beforeMint = () => {
+ entered();
+ return held;
+ };
+ const app = appAs("user", actor);
+ const started = authorize(app, personalApp);
+ await minting;
+ // The attempt is standing with no auth_id while the vendor has not answered yet.
+ expect(await attemptsFor(actor)).toMatchObject([{ authId: null }]);
+ const cancelled = await cancel(app, personalApp);
+ expect(cancelled.status).toBe(200);
+ expect(await cancelled.json()).toEqual({ cancelled: true });
+ release();
+ const response = await started;
+ const text = await response.text();
+ // The vendor did mint, and nothing stands behind it here.
+ expect(mints).toHaveLength(1);
+ expect(await attemptsFor(actor)).toEqual([]);
+ expect(text).not.toContain("bearer-capability");
+ expect(text).not.toContain("ar_minted_1");
+ expect(response.status).toBe(409);
+ const body = JSON.parse(text) as {
+ error?: string;
+ authorizationUrl?: string;
+ };
+ expect(body.authorizationUrl).toBeUndefined();
+ expect(body.error).toContain("cancelled");
+ // Nothing holds the actor: starting over mints again and binds the new attempt.
+ beforeMint = null;
+ mints = [];
+ nextAuthId = "ar_after_cancel";
+ const again = await authorize(app, personalApp);
+ expect(again.status).toBe(200);
+ expect(await again.json()).toEqual({ authorizationUrl: SECRET_URL });
+ expect(await attemptsFor(actor)).toMatchObject([
+ { authId: "ar_after_cancel", completedAt: null },
+ ]);
+ });
+
+ test("whatever the reason the write declined, the consent url is not handed out", async () => {
+ const actor = await person("mint-declined");
+ const response = await authorize(
+ appAs("user", actor, "arcade", {
+ recordAuthorizationMint: async () => false,
+ }),
+ personalApp,
+ );
+ const text = await response.text();
+ expect(mints).toHaveLength(1);
+ expect(text).not.toContain("bearer-capability");
+ expect(text).not.toContain("ar_minted_1");
+ expect(response.status).toBe(409);
+ expect(
+ (JSON.parse(text) as { authorizationUrl?: string }).authorizationUrl,
+ ).toBeUndefined();
+ });
+ });
+});
diff --git a/server/tests/arcade-authorize-status-engine.test.ts b/server/tests/arcade-authorize-status-engine.test.ts
new file mode 100644
index 000000000..b51cd6a76
--- /dev/null
+++ b/server/tests/arcade-authorize-status-engine.test.ts
@@ -0,0 +1,201 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import { BrokerRefusalError } from "../src/plugins/broker";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+
+/**
+ * WHAT THE VENDOR SAID ABOUT THE AUTHORIZATION IT JUST MINTED, READ OFF A REAL HTTP ANSWER.
+ *
+ * The rest of the authorize suite replaces `Tools.prototype.authorize` with a function returning a
+ * hand-built object. That is blind to this class: the stub returns whatever shape the test chose,
+ * so it cannot show what happens when the LIVE engine answers `{"status":"completed"}` — the answer
+ * Arcade gives when the account already holds this grant — or `{"status":"failed"}`. This file
+ * serves those answers as JSON over a socket through `ARCADE_BASE_URL`, exactly as
+ * `arcade-pagination-engine.test.ts` does, and `new Arcade({ apiKey })` reads that variable at
+ * construction, which is why the engine starts BEFORE `createArcadeClient`.
+ */
+
+const account: AccountRef = {
+ holder: "person",
+ userId: "u1",
+ vendorUserId: "vendor-u1",
+};
+
+type EngineOptions = {
+ /**
+ * The `status` the mint answers with; `null` OMITS the field, as the wire may. Anything else is
+ * serialized as it stands, so a status that is not text reaches the adapter as the wire sent it.
+ */
+ status: unknown;
+ /** Send `"status": null` explicitly rather than omitting the key. */
+ explicitNull?: true;
+ /** Whether the mint answers a consent url beside it. */
+ url?: string | null;
+};
+
+type Engine = { port: number; stop: () => void };
+
+function startEngine(options: EngineOptions): Engine {
+ const server = Bun.serve({
+ port: 0,
+ fetch(request) {
+ const url = new URL(request.url);
+ if (url.pathname === "/v1/tools/authorize") {
+ const body: Record = { id: "ar_mint1" };
+ if (options.status !== null) body.status = options.status;
+ else if (options.explicitNull) body.status = null;
+ const consent =
+ options.url === undefined
+ ? "https://consent.example/go"
+ : options.url;
+ if (consent !== null) body.url = consent;
+ return Response.json(body);
+ }
+ return new Response("not found", { status: 404 });
+ },
+ });
+ return { port: server.port, stop: () => server.stop(true) };
+}
+
+let running: Engine | null = null;
+const wasBaseURL = process.env.ARCADE_BASE_URL;
+
+function engine(options: EngineOptions): Engine {
+ running = startEngine(options);
+ process.env.ARCADE_BASE_URL = `http://localhost:${running.port}`;
+ return running;
+}
+
+afterEach(() => {
+ running?.stop();
+ running = null;
+ useArcadeClient(null);
+ if (wasBaseURL === undefined) delete process.env.ARCADE_BASE_URL;
+ else process.env.ARCADE_BASE_URL = wasBaseURL;
+});
+
+const mint = () =>
+ createArcadeClient("never-dialled").broker.authorize({
+ account,
+ toolkit: "Gmail",
+ action: "Gmail.SendEmail",
+ returnUrl: "https://openbot.example/settings/plugins",
+ });
+
+describe("the mint reads the status Arcade answered with", () => {
+ test("a pending mint is handed on, which is the whole of the happy path", async () => {
+ engine({ status: "pending" });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+
+ test("an answer with NO status is handed on: silence is not a no", async () => {
+ engine({ status: null });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+
+ test("a mint the vendor calls completed does not send anybody to a consent screen", async () => {
+ // Arcade answers this for an account that already holds the grant, and it answers it WITH a
+ // url: the defect is not an absent url, so the url guard below cannot stand in for this.
+ engine({ status: "completed" });
+ await expect(mint()).rejects.toBeInstanceOf(BrokerRefusalError);
+ await expect(mint()).rejects.toThrow(/already/i);
+ });
+
+ test("a mint the vendor calls failed is not handed on as a live redirect", async () => {
+ engine({ status: "failed" });
+ await expect(mint()).rejects.toBeInstanceOf(BrokerRefusalError);
+ await expect(mint()).rejects.toThrow(/failed/);
+ });
+
+ test("a status this deployment does not know is not read as consent-is-next", async () => {
+ // The spelling is not in the SDK's declared union at all. An allowlist refuses it; a denylist
+ // of the dead spellings would hand its url to a browser.
+ engine({ status: "revoked" });
+ await expect(mint()).rejects.toBeInstanceOf(BrokerRefusalError);
+ });
+
+ /*
+ * D6. Every assertion above holds whether or not the status is FOLDED: an unfolded `" Completed "`
+ * is a spelling outside the allowlist and is refused anyway, so the old single row here could not
+ * fail on a deleted `.trim().toLowerCase()`. The rows below can: a padded or upper-cased member of
+ * the ALLOWLIST is handed on only when the read folds it, and the held-grant SENTENCE ("already")
+ * is reached only by a folded `completed`.
+ */
+ test("the status is read case- and space-insensitively, as every other comparison here is", async () => {
+ engine({ status: " Completed " });
+ await expect(mint()).rejects.toBeInstanceOf(BrokerRefusalError);
+ await expect(mint()).rejects.toThrow(/already/i);
+ });
+
+ test("a padded, re-cased pending is still a consent to walk through", async () => {
+ engine({ status: " Pending " });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+
+ test("not_started — the other half of the allowlist, and what a first mint answers — is handed on", async () => {
+ engine({ status: "not_started" });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+
+ test("a padded, upper-cased not_started is handed on too", async () => {
+ engine({ status: " NOT_STARTED " });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+
+ test("an explicit null status is silence, exactly like an omitted one", async () => {
+ engine({ status: null, explicitNull: true });
+ expect(await mint()).toEqual({
+ redirectUrl: "https://consent.example/go",
+ authorizationId: "ar_mint1",
+ });
+ });
+});
+
+/**
+ * L2-b. A STATUS THAT IS PRESENT AND IS NOT STATUS TEXT IS NOT SILENCE. It once read as silence —
+ * `textOf` answered `undefined` for it, and "silence is not a no" handed the bearer url beside it
+ * to a browser as a live redirect, after a status the code could not read. It is refused, as a
+ * DEFINITE refusal (a `BrokerRefusalError`), so the attempt is discarded and Authorize can be
+ * pressed again at once.
+ */
+describe("a mint status this deployment cannot read is not handed on", () => {
+ for (const [label, status] of [
+ ["an object", {}],
+ ["false", false],
+ ["a number", 0],
+ ["a list", []],
+ ["a blank string", ""],
+ ["whitespace", " "],
+ ] as const) {
+ test(`${label} where the status belongs refuses, and the url is not returned`, async () => {
+ engine({ status });
+ const outcome = await mint().then(
+ (minted) => ({ minted }),
+ (error: unknown) => ({ error }),
+ );
+ expect("minted" in outcome).toBe(false);
+ expect((outcome as { error: unknown }).error).toBeInstanceOf(
+ BrokerRefusalError,
+ );
+ expect(String((outcome as { error: Error }).error.message)).not.toContain(
+ "consent.example",
+ );
+ });
+ }
+});
diff --git a/server/tests/arcade-classify.test.ts b/server/tests/arcade-classify.test.ts
new file mode 100644
index 000000000..af34b7e42
--- /dev/null
+++ b/server/tests/arcade-classify.test.ts
@@ -0,0 +1,47 @@
+import { describe, expect, test } from "bun:test";
+import { classify } from "../src/plugins/arcade";
+
+/**
+ * What an Arcade tool does, read from `metadata.behavior`. `destructive` is read FIRST: a reading
+ * that checked `read_only` first would record a both-flagged tool as a read, the one direction
+ * nothing downstream recovers from. Anything that cannot be read fails closed to a destructive
+ * write, which costs an approval in front of a read rather than a deletion let through unannounced.
+ */
+describe("classify", () => {
+ test("destructive is a destructive write", () => {
+ expect(classify({ behavior: { destructive: true } })).toEqual({
+ effect: "write",
+ destructive: true,
+ });
+ });
+
+ test("read_only alone is a read", () => {
+ expect(classify({ behavior: { read_only: true } })).toEqual({
+ effect: "read",
+ destructive: false,
+ });
+ });
+
+ test("both flags true is a destructive write, never a read", () => {
+ expect(
+ classify({ behavior: { destructive: true, read_only: true } }),
+ ).toEqual({ effect: "write", destructive: true });
+ });
+
+ test("neither flag true is a plain write", () => {
+ expect(
+ classify({ behavior: { destructive: false, read_only: false } }),
+ ).toEqual({ effect: "write", destructive: false });
+ });
+
+ test("absent metadata fails closed", () => {
+ expect(classify(undefined)).toEqual({ effect: "write", destructive: true });
+ });
+
+ test("a string-valued flag fails closed rather than coercing", () => {
+ expect(classify({ behavior: { read_only: "true" } })).toEqual({
+ effect: "write",
+ destructive: true,
+ });
+ });
+});
diff --git a/server/tests/arcade-completion-transaction.integration.test.ts b/server/tests/arcade-completion-transaction.integration.test.ts
new file mode 100644
index 000000000..0cef5f8eb
--- /dev/null
+++ b/server/tests/arcade-completion-transaction.integration.test.ts
@@ -0,0 +1,572 @@
+// server/tests/arcade-completion-transaction.integration.test.ts
+import { afterAll, beforeAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { and, eq, like } from "drizzle-orm";
+import { createAuditStore } from "../src/audit";
+import { createDatabase } from "../src/db/client";
+import {
+ auditEvents,
+ brokeredConnections,
+ mcpServers,
+ pendingAuthorizations,
+} from "../src/db/schema";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+import { createPluginStore } from "../src/plugins/store";
+import { testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+
+/*
+ * FOUR CONNECTIONS RATHER THAN THE SHARED `TEST_POOL` OF TWO, because the race below drives two
+ * completion TRANSACTIONS at once and the second one blocks on the first's row lock while holding
+ * its own connection. Two is the exact floor for that pair and leaves nothing for the reads that
+ * follow if the driver has not handed a connection back yet; `support/database.ts` says a file
+ * wanting a different pool asks for one itself.
+ */
+const database = createDatabase(testDatabaseUrl(), { max: 4 });
+const suite = randomUUID().slice(0, 8);
+const serverId = `arcade-completion-${suite}`;
+
+const broker: ConnectedAppBroker = {
+ // Arcade holds the grant at the vendor, not locally; see `ConnectedAppBroker.grantIsLocal`.
+ grantIsLocal: false,
+ // Nothing here asks what auth provider a connection is held under.
+ authProviderOf: async () => null,
+ // Arcade runs the correlated, pollable flow, so this stub DECLARES the sub-surface rather
+ // than a null; nothing here reaches either member. See `ConnectedAppBroker.authorizations`.
+ authorizations: {
+ status: async () => {
+ throw new Error("nothing here polls an authorization");
+ },
+ confirmReturn: async () => {
+ throw new Error("nothing here confirms a return leg");
+ },
+ },
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async () => ({
+ redirectUrl: "https://vendor.example/consent",
+ authorizationId: "ar_stub",
+ }),
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => "acme-bot",
+};
+
+const policy = { mode: "enforce" as const, deny: [], allow: ["true"] };
+
+const storeOn = (handle: typeof database) =>
+ createPluginStore({
+ brokerProvider: "arcade",
+ database: handle,
+ auditStore: createAuditStore(handle),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => policy,
+ broker,
+ deploymentId: `acme-${suite}`,
+ sharedUse: async () => ({ allowed: true }),
+ });
+
+const store = storeOn(database);
+
+/**
+ * The same store with the AUDIT INSERT made to throw, and nothing else changed.
+ *
+ * The failure is injected at the DATABASE HANDLE rather than at the store's `auditStore` option,
+ * because the audit write that has to be atomic with the connection write is the one bound to the
+ * transaction — a throwing `auditStore` passed into `createPluginStore` would never be the object
+ * that write goes through, and the test would prove nothing. This proxy lets the connection upsert
+ * through and refuses the `audit_events` insert, so it fires BETWEEN the two writes, which is where
+ * the rollback has to hold.
+ */
+const brokenAuditDatabase = new Proxy(database, {
+ get(target, property, receiver) {
+ if (property !== "transaction") {
+ const value = Reflect.get(target, property, receiver);
+ return typeof value === "function" ? value.bind(target) : value;
+ }
+ return (work: (tx: unknown) => Promise) =>
+ target.transaction((transaction) =>
+ work(
+ new Proxy(transaction, {
+ get(txTarget, txProperty, txReceiver) {
+ if (txProperty === "insert") {
+ return (table: unknown) => {
+ if (table === auditEvents)
+ throw new Error("injected audit failure");
+ return txTarget.insert(table as never);
+ };
+ }
+ const value = Reflect.get(txTarget, txProperty, txReceiver);
+ return typeof value === "function" ? value.bind(txTarget) : value;
+ },
+ }),
+ ),
+ );
+ },
+}) as typeof database;
+
+const brokenAuditStore = storeOn(brokenAuditDatabase);
+
+const account = (actor: string): AccountRef => ({
+ holder: "person",
+ userId: actor,
+ vendorUserId: `v-${actor}`,
+});
+
+/**
+ * An actor id for this suite, AND THE PERSON BEHIND IT.
+ *
+ * `pending_authorizations.actor_id` keys to `users` and cascades (`.user_id` must equal it), so an
+ * attempt cannot exist for somebody who does not. Seeded here so each case stays about the completion transaction
+ * rather than about the foreign key.
+ */
+const people: string[] = [];
+const actor = async (name: string) => {
+ const id = `${name}-${suite}`;
+ people.push(id);
+ await seedPeople(database, [id]);
+ return id;
+};
+const toolkit = (name: string) => `tk-${name}-${suite}`;
+
+/** A claimed attempt carrying a minted `ar_...`, which is what a callback arrives against. */
+const attemptWith = async (actorId: string, authId: string) => {
+ const claimed = await store.claimAuthorizationAttempt({
+ actorId,
+ serverId,
+ action: "Google.SendEmail",
+ account: account(actorId),
+ });
+ if (!claimed.claimed) throw new Error("claim failed");
+ const minted = await store.recordAuthorizationMint({
+ actorId,
+ createdAt: claimed.attempt.createdAt,
+ authId,
+ });
+ if (!minted) throw new Error("mint failed");
+ return claimed.attempt;
+};
+
+const connectionsFor = (app: string) =>
+ database
+ .select()
+ .from(brokeredConnections)
+ .where(
+ and(
+ eq(brokeredConnections.provider, "arcade"),
+ eq(brokeredConnections.app, app),
+ ),
+ );
+
+const connectedEventsFor = (app: string) =>
+ database
+ .select()
+ .from(auditEvents)
+ .where(
+ and(
+ eq(auditEvents.eventType, "mcp.account_connected"),
+ eq(auditEvents.targetId, app),
+ ),
+ );
+
+const attemptRow = async (actorId: string) => {
+ const [row] = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(eq(pendingAuthorizations.actorId, actorId));
+ if (!row) throw new Error("attempt row gone");
+ return row;
+};
+
+beforeAll(async () => {
+ await database.insert(mcpServers).values({
+ id: serverId,
+ title: "Completion transaction",
+ vendor: "Arcade",
+ url: "https://example.invalid/mcp",
+ });
+});
+
+afterAll(async () => {
+ // Cascades to the attempts. The connections are keyed on the app, not the server, so they go
+ // separately; the audit rows cannot go at all, which is what `audit_events_append_only` is for.
+ await database.delete(mcpServers).where(eq(mcpServers.id, serverId));
+ await database
+ .delete(brokeredConnections)
+ .where(like(brokeredConnections.app, `%-${suite}`));
+ await removePeople(database, people);
+ await database.$client.close();
+});
+
+describe("completeAuthorizationAttempt", () => {
+ test("one call writes exactly one connection and one audit event naming the action and scopes", async () => {
+ const a = await actor("single");
+ const app = toolkit("single");
+ await attemptWith(a, "ar_single");
+
+ await store.completeAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_single",
+ toolkit: app,
+ account: account(a),
+ action: "Google.SendEmail",
+ scopes: ["gmail.send", "gmail.readonly"],
+ authProvider: "google",
+ });
+
+ const connections = await connectionsFor(app);
+ expect(connections.length).toBe(1);
+ expect(connections[0]?.holder).toBe("person");
+ expect(connections[0]?.userId).toBe(a);
+ expect(connections[0]?.vendorUserId).toBe(`v-${a}`);
+ expect(connections[0]?.verified).toBe(true);
+ expect(connections[0]?.verifiedAt).not.toBeNull();
+ expect(connections[0]?.probeAction).toBeNull();
+ // A PERSON'S ROW IS ATTRIBUTED BY `user_id` AND NEVER BY `connected_by`, which is the
+ // deployment-held column: `deploymentConnectionFor` is the only reader and it reads only
+ // `holder = 'deployment'`. Writing the actor here would put an id in a column no reader of a
+ // person's row consults, on the row that already names them.
+ expect(connections[0]?.connectedBy).toBeNull();
+
+ const events = await connectedEventsFor(app);
+ expect(events.length).toBe(1);
+ const payload = events[0]?.payload as Record;
+ expect(payload.action).toBe("Google.SendEmail");
+ expect(payload.scope).toBe("gmail.send gmail.readonly");
+ expect(payload.server).toBe(app);
+ expect(payload.holder).toBe("person");
+ expect(payload.authProvider).toBe("google");
+ expect(payload).not.toHaveProperty("provider");
+
+ const attempt = await attemptRow(a);
+ expect(attempt.outcome).toBe("completed");
+ expect(attempt.completedAt).not.toBeNull();
+ });
+
+ /*
+ * WHO CONNECTED THE TEAM ACCOUNT. A Shared app's row is the one place an administrator can read
+ * that from, and a consent completed through Arcade used to leave it permanently null — so
+ * `deploymentConnectionFor` answered that nobody connected an account the whole deployment acts
+ * as. The actor is the session subject (`users.id`) the attempt is keyed on, which is the id the
+ * admin read joins `users` on for an email.
+ */
+ test("a DEPLOYMENT-held completion records the administrator who consented, and a later one supersedes", async () => {
+ const first = await actor("shared-first");
+ const second = await actor("shared-second");
+ const app = toolkit("shared");
+ const deployment: AccountRef = {
+ holder: "deployment",
+ vendorUserId: `v-${app}`,
+ };
+
+ const claimFor = async (actorId: string, authId: string) => {
+ const claimed = await store.claimAuthorizationAttempt({
+ actorId,
+ serverId,
+ action: "Google.SendEmail",
+ account: deployment,
+ });
+ if (!claimed.claimed) throw new Error("claim failed");
+ const minted = await store.recordAuthorizationMint({
+ actorId,
+ createdAt: claimed.attempt.createdAt,
+ authId,
+ });
+ if (!minted) throw new Error("mint failed");
+ };
+ const complete = (actorId: string, authId: string) =>
+ store.completeAuthorizationAttempt({
+ actorId,
+ authId,
+ toolkit: app,
+ account: deployment,
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ });
+
+ await claimFor(first, "ar_shared_first");
+ await complete(first, "ar_shared_first");
+
+ let connections = await connectionsFor(app);
+ expect(connections.length).toBe(1);
+ expect(connections[0]?.holder).toBe("deployment");
+ expect(connections[0]?.userId).toBeNull();
+ expect(connections[0]?.connectedBy).toBe(first);
+
+ // AND THE SECOND ADMINISTRATOR SUPERSEDES THE FIRST, because a deployment account is shared
+ // and the row credits whoever most recently consented for it. The insert takes the conflict
+ // branch here, which is the half that used to keep an attribution standing while rewriting
+ // everything around it.
+ await claimFor(second, "ar_shared_second");
+ await complete(second, "ar_shared_second");
+
+ connections = await connectionsFor(app);
+ expect(connections.length).toBe(1);
+ expect(connections[0]?.connectedBy).toBe(second);
+ });
+
+ /*
+ * THE ROW NAMES THE IDENTITY THE LATEST CONSENT WAS GIVEN UNDER. Calls go out under the row's
+ * `vendor_user_id` (`connectionTokenFor` reads it back rather than recomputing it), so a
+ * re-consent under a new identity that rewrote `verified`/`verified_at` and left the old id
+ * standing sent every later call to an account this consent says nothing about. Both holders,
+ * because both conflict arms used to omit it.
+ */
+ test.each(["deployment", "person"] as const)(
+ "a %s re-consent under a NEW vendor identity moves the row to it",
+ async (holder) => {
+ const a = await actor(`reid-${holder}`);
+ const app = toolkit(`reid-${holder}`);
+ const as = (vendorUserId: string): AccountRef =>
+ holder === "deployment"
+ ? { holder, vendorUserId }
+ : { holder, userId: a, vendorUserId };
+
+ const consent = async (authId: string, ref: AccountRef) => {
+ const claimed = await store.claimAuthorizationAttempt({
+ actorId: a,
+ serverId,
+ action: "Google.SendEmail",
+ account: ref,
+ });
+ if (!claimed.claimed) throw new Error("claim failed");
+ const minted = await store.recordAuthorizationMint({
+ actorId: a,
+ createdAt: claimed.attempt.createdAt,
+ authId,
+ });
+ if (!minted) throw new Error("mint failed");
+ await store.completeAuthorizationAttempt({
+ actorId: a,
+ authId,
+ toolkit: app,
+ account: ref,
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ });
+ };
+
+ await consent(`ar_reid_${holder}_1`, as(`old-${app}`));
+ await consent(`ar_reid_${holder}_2`, as(`new-${app}`));
+
+ const connections = await connectionsFor(app);
+ expect(connections.length).toBe(1);
+ expect(connections[0]?.vendorUserId).toBe(`new-${app}`);
+ },
+ );
+
+ test("two CONCURRENT calls write exactly one connection and one audit event, and the loser answers as the winner would", async () => {
+ const a = await actor("race");
+ const app = toolkit("race");
+ await attemptWith(a, "ar_race");
+
+ const call = () =>
+ store.completeAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_race",
+ toolkit: app,
+ account: account(a),
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ });
+
+ // CONCURRENTLY, on the real pool. A sequential replay passes against an implementation with no
+ // serialization at all, so it would be no evidence about this method.
+ const answers = await Promise.all([call(), call()]);
+ // The loser is indistinguishable from the winner by its answer, which is the point: a replay is
+ // a no-op and not an error.
+ expect(answers).toEqual([undefined, undefined]);
+
+ expect((await connectionsFor(app)).length).toBe(1);
+ expect((await connectedEventsFor(app)).length).toBe(1);
+
+ const attempt = await attemptRow(a);
+ expect(attempt.outcome).toBe("completed");
+ expect(attempt.completedAt).not.toBeNull();
+ });
+
+ test("a failure between the connection write and the audit write persists NEITHER", async () => {
+ const a = await actor("rollback");
+ const app = toolkit("rollback");
+ await attemptWith(a, "ar_rollback");
+
+ await expect(
+ brokenAuditStore.completeAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_rollback",
+ toolkit: app,
+ account: account(a),
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ }),
+ ).rejects.toThrow("injected audit failure");
+
+ // BOTH tables, because "it returned" is not evidence that nothing was written.
+ expect((await connectionsFor(app)).length).toBe(0);
+ expect((await connectedEventsFor(app)).length).toBe(0);
+
+ // And the attempt is still claimable, because its UPDATE was in the same transaction.
+ const attempt = await attemptRow(a);
+ expect(attempt.completedAt).toBeNull();
+ expect(attempt.outcome).toBeNull();
+ });
+
+ test("an auth_id that does not match the row writes nothing and leaves the row UNCHANGED", async () => {
+ const a = await actor("mismatch");
+ const app = toolkit("mismatch");
+ await attemptWith(a, "ar_real");
+
+ await store.completeAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_someone_elses",
+ toolkit: app,
+ account: account(a),
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ });
+
+ expect((await connectionsFor(app)).length).toBe(0);
+ expect((await connectedEventsFor(app)).length).toBe(0);
+
+ // THE ROW'S OWN STATE, not a return value.
+ const attempt = await attemptRow(a);
+ expect(attempt.completedAt).toBeNull();
+ expect(attempt.outcome).toBeNull();
+ expect(attempt.authId).toBe("ar_real");
+ });
+});
+
+describe("failAuthorizationAttempt / markAuthorizationUnreconciled", () => {
+ test("mark an attempt failed, matched on (actor_id, auth_id)", async () => {
+ const a = await actor("failed");
+ await attemptWith(a, "ar_failed");
+
+ expect(
+ await store.failAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_other",
+ }),
+ ).toBe(false);
+ let attempt = await attemptRow(a);
+ expect(attempt.outcome).toBeNull();
+ expect(attempt.completedAt).toBeNull();
+
+ expect(
+ await store.failAuthorizationAttempt({ actorId: a, authId: "ar_failed" }),
+ ).toBe(true);
+ attempt = await attemptRow(a);
+ expect(attempt.outcome).toBe("failed");
+ expect(attempt.completedAt).not.toBeNull();
+ });
+
+ test("mark an attempt unreconciled, matched on (actor_id, auth_id)", async () => {
+ const a = await actor("unreconciled");
+ await attemptWith(a, "ar_unreconciled");
+
+ expect(
+ await store.markAuthorizationUnreconciled({
+ actorId: a,
+ authId: "ar_other",
+ }),
+ ).toBe(false);
+ expect((await attemptRow(a)).outcome).toBeNull();
+
+ expect(
+ await store.markAuthorizationUnreconciled({
+ actorId: a,
+ authId: "ar_unreconciled",
+ }),
+ ).toBe(true);
+ const attempt = await attemptRow(a);
+ expect(attempt.outcome).toBe("unreconciled");
+ expect(attempt.completedAt).not.toBeNull();
+ });
+
+ /*
+ * THE CASE THE `(actor_id, auth_id)` MATCH DOES NOT EXCLUDE ON ITS OWN. A completed attempt is
+ * still holding the `ar_...` the failing leg matches on, so without `completed_at IS NULL` this
+ * close rewrote a person's SUCCESSFUL authorization as a failure — while the connection row and
+ * the `mcp.account_connected` entry written by that completion stood. The attempt and the trail
+ * then disagreed, and the attempt was the one that was wrong.
+ *
+ * Sequential rather than concurrent, deliberately, and it is not a weaker test: the failing leg
+ * is a LATE callback and the thing under test is the state of the committed row it arrives at,
+ * not a lock ordering. Concurrency is what `completeAuthorizationAttempt`'s own race test proves.
+ */
+ test("a late failure callback does NOT reopen an attempt that already completed", async () => {
+ const a = await actor("late-fail");
+ const app = toolkit("late-fail");
+ await attemptWith(a, "ar_late_fail");
+
+ await store.completeAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_late_fail",
+ toolkit: app,
+ account: account(a),
+ action: "Google.SendEmail",
+ scopes: ["gmail.send"],
+ authProvider: "google",
+ });
+ const completed = await attemptRow(a);
+ expect(completed.outcome).toBe("completed");
+
+ /*
+ * THE ROW IS ASSERTED BEFORE THE RETURN VALUE, so a regression here reads as the corruption it
+ * is rather than as a boolean that came back the wrong way. The boolean is checked too, below.
+ */
+ const closed = await store.failAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_late_fail",
+ });
+
+ const after = await attemptRow(a);
+ expect(after.outcome).toBe("completed");
+ // NOT MERELY "still terminal": the timestamp must be the completion's own, because re-stamping
+ // it would move when this grant was obtained even where the verdict survived.
+ expect(after.completedAt).toEqual(completed.completedAt);
+ // The grant the attempt is the record OF is untouched, which is the half a reader checks next.
+ expect((await connectionsFor(app)).length).toBe(1);
+ expect((await connectedEventsFor(app)).length).toBe(1);
+ expect(closed).toBe(false);
+ });
+
+ /* The same rule among the three non-completion verdicts: the first one to settle the row wins. */
+ test("a second verdict does not overwrite the first one an attempt settled on", async () => {
+ const a = await actor("settled");
+ await attemptWith(a, "ar_settled");
+
+ expect(
+ await store.failAuthorizationAttempt({
+ actorId: a,
+ authId: "ar_settled",
+ }),
+ ).toBe(true);
+ const failed = await attemptRow(a);
+
+ const withdrew = await store.markAuthorizationWithdrawn({
+ actorId: a,
+ authId: "ar_settled",
+ });
+ const unreconciled = await store.markAuthorizationUnreconciled({
+ actorId: a,
+ authId: "ar_settled",
+ });
+
+ const after = await attemptRow(a);
+ expect(after.outcome).toBe("failed");
+ expect(after.completedAt).toEqual(failed.completedAt);
+ expect([withdrew, unreconciled]).toEqual([false, false]);
+ });
+});
diff --git a/server/tests/arcade-confirm-engine.test.ts b/server/tests/arcade-confirm-engine.test.ts
new file mode 100644
index 000000000..d3c3261fe
--- /dev/null
+++ b/server/tests/arcade-confirm-engine.test.ts
@@ -0,0 +1,94 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+
+/**
+ * THE RETURN LEG AGAINST A REAL HTTP ENGINE, BECAUSE NOTHING LESS CAN SEE THIS CLASS OF DEFECT.
+ *
+ * `arcade-adapter-authorize.test.ts` stubs the SDK's `Auth` PROTOTYPE and hands `confirmUser` a
+ * hand-written object, so every field that file writes is a field the adapter is guaranteed to
+ * find. The wire makes no such guarantee: `ConfirmUserResponse.auth_id` is DECLARED required in
+ * the SDK's types and is not VALIDATED anywhere in it, so a vendor answer that omits the field
+ * reaches the adapter as `undefined` wearing the type `string` — the same hole the mint closes by
+ * hand at `arcade-adapter.ts`'s `authorize`.
+ *
+ * So these tests serve real JSON over a real socket through `ARCADE_BASE_URL` and let the SDK
+ * build and parse its own request. `new Arcade({ apiKey })` reads that variable at construction,
+ * which is why the server is started BEFORE `createArcadeClient`.
+ */
+
+const CONFIRM_PATH = "/v1/auth/confirm_user";
+
+type Engine = {
+ port: number;
+ /** Every path the SDK asked for, in order. */
+ requests: string[];
+ stop: () => void;
+};
+
+/** Serve `body` (verbatim JSON) from the confirm endpoint. */
+function startEngine(body: unknown): Engine {
+ const requests: string[] = [];
+ const server = Bun.serve({
+ port: 0,
+ fetch(request) {
+ const url = new URL(request.url);
+ requests.push(url.pathname);
+ if (url.pathname === CONFIRM_PATH) return Response.json(body);
+ return new Response("not found", { status: 404 });
+ },
+ });
+ return { port: server.port, requests, stop: () => server.stop(true) };
+}
+
+let running: Engine | null = null;
+const wasBaseURL = process.env.ARCADE_BASE_URL;
+
+function engine(body: unknown): Engine {
+ running = startEngine(body);
+ process.env.ARCADE_BASE_URL = `http://localhost:${running.port}`;
+ return running;
+}
+
+afterEach(() => {
+ running?.stop();
+ running = null;
+ useArcadeClient(null);
+ if (wasBaseURL === undefined) delete process.env.ARCADE_BASE_URL;
+ else process.env.ARCADE_BASE_URL = wasBaseURL;
+});
+
+const confirm = () => {
+ const { broker } = createArcadeClient("never-dialled");
+ const flow = broker.authorizations;
+ if (!flow) throw new Error("the Arcade broker runs an authorization flow.");
+ return flow.confirmReturn({ handle: "fl_1", vendorUserId: "vendor-u1" });
+};
+
+describe("confirmReturn answers an authorization id or it answers nothing", () => {
+ test("a confirmation that OMITS auth_id throws rather than answering an id-less match", async () => {
+ const e = engine({ next_uri: "https://cloud.arcade.dev/done" });
+
+ await expect(confirm()).rejects.toThrow(/no authorization id/);
+ // The call was really made: this is the vendor's answer being refused, not a short circuit.
+ expect(e.requests).toEqual([CONFIRM_PATH]);
+ });
+
+ test("an EMPTY auth_id is refused on the same footing as an absent one", async () => {
+ engine({ auth_id: "" });
+
+ await expect(confirm()).rejects.toThrow(/no authorization id/);
+ });
+
+ test("a non-string auth_id is refused rather than carried into the binding", async () => {
+ engine({ auth_id: 7 });
+
+ await expect(confirm()).rejects.toThrow(/no authorization id/);
+ });
+
+ test("the id the vendor really answered rides out untouched", async () => {
+ engine({ auth_id: "ar_7", next_uri: "https://cloud.arcade.dev/done" });
+
+ expect(await confirm()).toEqual({ authorizationId: "ar_7" });
+ });
+});
diff --git a/server/tests/arcade-connection-kind-engine.test.ts b/server/tests/arcade-connection-kind-engine.test.ts
new file mode 100644
index 000000000..a1d1d133f
--- /dev/null
+++ b/server/tests/arcade-connection-kind-engine.test.ts
@@ -0,0 +1,182 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { BrokerApp } from "../src/plugins/broker";
+
+/**
+ * WHICH FLOW AN ARCADE APP GETS IS READ OFF THE LISTING, AND THIS IS THE FILE THAT WATCHES IT.
+ *
+ * `listApps` used to write `connection: { kind: "consent" }` onto every toolkit in the catalogue,
+ * which is a claim about the vendor made without reading the vendor. `Apollo` — verbatim below,
+ * from `fixtures/arcade-catalogue.json` — publishes NO `requirements.authorization` at all and one
+ * deployment-wide secret instead, so it was announced as an app somebody connects by clicking
+ * through a consent screen when there is no consent screen for it anywhere: pressing Connect mints
+ * an authorization for a tool that requires none.
+ *
+ * SERVED OVER A REAL SOCKET THROUGH `ARCADE_BASE_URL`, as `arcade-pagination-engine.test.ts` does
+ * and for its reason: the adapter suite's prototype stubs hand the adapter whatever shape the test
+ * felt like, and the shapes this file is about — a `requirements` the SDK's own types forbid — are
+ * exactly the ones a stub cannot be trusted to reproduce.
+ */
+
+type Engine = { port: number; stop: () => void };
+
+const toolkitOf = (name: string) => ({
+ name,
+ description: `Arcade tools designed for LLMs to interact with ${name}`,
+ version: "1.0.0",
+});
+
+/** Verbatim from the live listing: an oauth2 authorization, which IS a consent flow. */
+const OAUTH_REQUIREMENTS = {
+ met: true,
+ authorization: {
+ id: "arcade-asana",
+ provider_id: "asana",
+ provider_type: "oauth2",
+ oauth2: { scopes: ["default"] },
+ status: "active",
+ },
+};
+
+/** Verbatim from the live listing: a deployment-wide secret and NO authorization whatsoever. */
+const SECRET_REQUIREMENTS = {
+ met: false,
+ secrets: [
+ {
+ key: "APOLLO_API_KEY",
+ met: false,
+ status_reason: "Secret APOLLO_API_KEY not found",
+ },
+ ],
+};
+
+const toolOf = (toolkit: string, n: number, extra: object = {}) => ({
+ qualified_name: `${toolkit}.Action${n}`,
+ name: `Action${n}`,
+ fully_qualified_name: `${toolkit}.Action${n}@1.0.0`,
+ toolkit: toolkitOf(toolkit),
+ input: { parameters: [] },
+ ...extra,
+});
+
+let running: Engine | null = null;
+const wasBaseURL = process.env.ARCADE_BASE_URL;
+
+function engine(tools: unknown[]): Engine {
+ const server = Bun.serve({
+ port: 0,
+ fetch(request) {
+ const url = new URL(request.url);
+ if (url.pathname !== "/v1/tools") {
+ return new Response("not found", { status: 404 });
+ }
+ const offset = Number(url.searchParams.get("offset") ?? "0");
+ const limit = Number(url.searchParams.get("limit") ?? "100");
+ const page = tools.slice(offset, offset + limit);
+ return Response.json({
+ items: page,
+ offset,
+ limit,
+ page_count: page.length,
+ total_count: tools.length,
+ });
+ },
+ });
+ running = { port: server.port, stop: () => server.stop(true) };
+ process.env.ARCADE_BASE_URL = `http://localhost:${server.port}`;
+ return running;
+}
+
+afterEach(() => {
+ running?.stop();
+ running = null;
+ if (wasBaseURL === undefined) delete process.env.ARCADE_BASE_URL;
+ else process.env.ARCADE_BASE_URL = wasBaseURL;
+});
+
+const appOf = (apps: BrokerApp[], slug: string): BrokerApp => {
+ const app = apps.find((a) => a.slug === slug);
+ if (!app) throw new Error(`no app ${slug} in the directory`);
+ return app;
+};
+
+const listed = () => createArcadeClient("never-dialled").broker.listApps();
+
+describe("the connection kind is read off what Arcade publishes", () => {
+ test("an app Arcade publishes an oauth2 authorization for is a consent app", async () => {
+ engine([toolOf("Asana", 0, { requirements: OAUTH_REQUIREMENTS })]);
+ expect(appOf(await listed(), "Asana").connection).toEqual({
+ kind: "consent",
+ });
+ });
+
+ test("an app that asks only for a deployment-wide secret is NOT a consent app", async () => {
+ // Apollo's live shape: a secret an operator sets once for everybody, and no authorization at
+ // all. There is no consent screen to send anybody to, so announcing one is a wrong answer.
+ engine([toolOf("Apollo", 0, { requirements: SECRET_REQUIREMENTS })]);
+ expect(appOf(await listed(), "Apollo").connection).toEqual({
+ kind: "no-auth",
+ });
+ });
+
+ test("an app that publishes no requirements at all asks nobody for anything", async () => {
+ engine([toolOf("Hackernews", 0)]);
+ expect(appOf(await listed(), "Hackernews").connection).toEqual({
+ kind: "no-auth",
+ });
+ });
+
+ test("one action's authorization makes the whole toolkit a consent app", async () => {
+ // A toolkit whose actions disagree still HAS a consent flow, and that flow is how somebody
+ // reaches the actions that need it.
+ engine([
+ toolOf("Mixed", 0, { requirements: SECRET_REQUIREMENTS }),
+ toolOf("Mixed", 1, { requirements: OAUTH_REQUIREMENTS }),
+ ]);
+ expect(appOf(await listed(), "Mixed").connection).toEqual({
+ kind: "consent",
+ });
+ });
+
+ test("the kinds are read per app, not written once for the catalogue", async () => {
+ engine([
+ toolOf("Asana", 0, { requirements: OAUTH_REQUIREMENTS }),
+ toolOf("Apollo", 0, { requirements: SECRET_REQUIREMENTS }),
+ toolOf("Hackernews", 0),
+ ]);
+ const apps = await listed();
+ expect(
+ Object.fromEntries(apps.map((a) => [a.slug, a.connection.kind])),
+ ).toEqual({ Asana: "consent", Apollo: "no-auth", Hackernews: "no-auth" });
+ });
+});
+
+describe("a requirements shape this deployment cannot read is refused, never guessed", () => {
+ test("requirements that are not an object refuse, naming the app", async () => {
+ engine([toolOf("Apollo", 0, { requirements: "yes" })]);
+ await expect(listed()).rejects.toThrow(
+ /Arcade sent a string where Apollo's requirements belong/,
+ );
+ });
+
+ test("an authorization that is not an object refuses, naming the app", async () => {
+ engine([toolOf("Asana", 0, { requirements: { authorization: "oauth2" } })]);
+ await expect(listed()).rejects.toThrow(
+ /Arcade sent a string where Asana's authorization requirement belongs/,
+ );
+ });
+
+ test("a list where requirements belong is refused as a list", async () => {
+ engine([toolOf("Asana", 0, { requirements: [] })]);
+ await expect(listed()).rejects.toThrow(
+ /Arcade sent a list where Asana's requirements belong/,
+ );
+ });
+
+ test("a null requirements is the vendor's silence and not a bad shape", async () => {
+ engine([toolOf("Hackernews", 0, { requirements: null })]);
+ expect(appOf(await listed(), "Hackernews").connection).toEqual({
+ kind: "no-auth",
+ });
+ });
+});
diff --git a/server/tests/arcade-directory-deadline-engine.test.ts b/server/tests/arcade-directory-deadline-engine.test.ts
new file mode 100644
index 000000000..515bd666f
--- /dev/null
+++ b/server/tests/arcade-directory-deadline-engine.test.ts
@@ -0,0 +1,233 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import { withVendorDeadline } from "../src/plugins/route-groups/context";
+
+/**
+ * "BROWSE ARCADE" MUST LOAD INSIDE ITS DEADLINE AGAINST A DIRECTORY OF ARCADE'S REAL SIZE.
+ *
+ * Every other `listApps` test serves a handful of rows, so nothing ever asked whether the listing
+ * finishes in time. Against the live API it could not: 8,320 tools read 100 at a time is 84
+ * SEQUENTIAL pages at ~1.2 s each, ~100 s, and the route wraps the whole of `listApps` in the 10 s
+ * `VENDOR_CALL_TIMEOUT_MS` — the browser got a 502 after 10,157 ms, every time.
+ *
+ * So this engine serves THE MEASURED DIRECTORY over a real socket, with the MEASURED LATENCY, and
+ * holds the listing to the deadline. Time is scaled down ×10 throughout so the suite stays fast;
+ * the RATIO between page latency and deadline is the live one:
+ *
+ * | measured live | here (÷10) |
+ * |----------------------------------------|--------------|
+ * | `total_count` 8,320 | 8,320 (same) |
+ * | ~1.19 s per page at limit ≤ 100 | 119 ms |
+ * | ~1.8 s per page at limit 250…1000 | 180 ms |
+ * | limit above 1000 REJECTED, empty body | same |
+ * | `VENDOR_CALL_TIMEOUT_MS` 10,000 ms | 1,000 ms |
+ */
+
+const SCALE = 10;
+/** `route-groups/context.ts`'s `VENDOR_CALL_TIMEOUT_MS`, scaled. */
+const DEADLINE_MS = 10_000 / SCALE;
+const DIRECTORY = 8_320;
+const TOOLKIT_SIZE = 52; // 8,320 / 52 = 160 toolkits
+
+const toolRow = (n: number) => {
+ const toolkit = `Kit${Math.floor(n / TOOLKIT_SIZE)}`;
+ return {
+ qualified_name: `${toolkit}.Action${n}`,
+ description: "",
+ toolkit: { name: toolkit, description: "" },
+ input: { parameters: [] },
+ };
+};
+
+type Asked = {
+ limit: number;
+ offset: number;
+ /** How many requests were in flight at the moment this one arrived, itself included. */
+ inFlightAtStart: number;
+ /** How many requests had already been ANSWERED when this one arrived. */
+ answeredBefore: number;
+};
+
+type Engine = {
+ asked: Asked[];
+ maxInFlight: () => number;
+ stop: () => void;
+};
+
+type EngineOptions = {
+ /** What `total_count` says on every page. */
+ claimed: number;
+ /** How many rows the engine actually serves, which may be fewer — or more — than it claims. */
+ served?: number;
+ /** Per-request latency in ms at limit ≤ 100, and above it. Defaults are the live ones, ÷10. */
+ latency?: { small: number; large: number };
+};
+
+const sleep = (ms: number) =>
+ new Promise((resolve) => setTimeout(resolve, ms));
+
+function startEngine(options: EngineOptions): Engine {
+ const asked: Asked[] = [];
+ const latency = options.latency ?? {
+ small: 1190 / SCALE,
+ large: 1800 / SCALE,
+ };
+ const served = options.served ?? options.claimed;
+ let inFlight = 0;
+ let peak = 0;
+ let answered = 0;
+ const server = Bun.serve({
+ port: 0,
+ async fetch(request) {
+ const url = new URL(request.url);
+ if (url.pathname !== "/v1/tools")
+ return new Response("not found", { status: 404 });
+ const offset = Number(url.searchParams.get("offset") ?? "0");
+ const limit = Number(url.searchParams.get("limit") ?? "25");
+ inFlight += 1;
+ peak = Math.max(peak, inFlight);
+ asked.push({
+ limit,
+ offset,
+ inFlightAtStart: inFlight,
+ answeredBefore: answered,
+ });
+ try {
+ // MEASURED: limit=5000 answered an EMPTY response in ~0.17 s. A page size past the
+ // vendor's ceiling must fail here exactly as it fails live.
+ if (limit > 1000) {
+ await sleep(170 / SCALE);
+ return new Response("", { status: 200 });
+ }
+ await sleep(limit <= 100 ? latency.small : latency.large);
+ const items = [];
+ for (let n = offset; n < Math.min(offset + limit, served); n++)
+ items.push(toolRow(n));
+ return Response.json({
+ items,
+ offset,
+ limit,
+ page_count: items.length,
+ total_count: options.claimed,
+ });
+ } finally {
+ inFlight -= 1;
+ answered += 1;
+ }
+ },
+ });
+ process.env.ARCADE_BASE_URL = `http://localhost:${server.port}`;
+ return { asked, maxInFlight: () => peak, stop: () => server.stop(true) };
+}
+
+let running: Engine | null = null;
+const wasBaseURL = process.env.ARCADE_BASE_URL;
+
+function engine(options: EngineOptions): Engine {
+ running = startEngine(options);
+ return running;
+}
+
+afterEach(() => {
+ running?.stop();
+ running = null;
+ useArcadeClient(null);
+ if (wasBaseURL === undefined) delete process.env.ARCADE_BASE_URL;
+ else process.env.ARCADE_BASE_URL = wasBaseURL;
+});
+
+/** `listApps` exactly as the "Browse Arcade" route calls it: under the vendor deadline. */
+const browse = () =>
+ withVendorDeadline(
+ () => createArcadeClient("never-dialled").broker.listApps(),
+ DEADLINE_MS,
+ );
+
+describe("the Arcade directory loads inside the vendor deadline at its real size", () => {
+ test("8,320 tools are listed in full before the deadline", async () => {
+ const e = engine({ claimed: DIRECTORY });
+ const started = performance.now();
+
+ const apps = await browse();
+
+ const took = performance.now() - started;
+ expect(apps.reduce((n, app) => n + app.actionCount, 0)).toBe(DIRECTORY);
+ expect(apps.length).toBe(DIRECTORY / TOOLKIT_SIZE);
+ // Comfortably inside, not merely inside: one page alone, then one wave of the rest.
+ expect(took).toBeLessThan(DEADLINE_MS);
+ expect(e.asked.length).toBe(9);
+ });
+
+ test("page one is asked for alone, then every remaining page at once", async () => {
+ const e = engine({ claimed: DIRECTORY });
+
+ await browse();
+
+ const [first, ...rest] = e.asked;
+ // Page one first, alone — its `total_count` is what says how many pages follow.
+ expect(first).toEqual({
+ limit: 1000,
+ offset: 0,
+ inFlightAtStart: 1,
+ answeredBefore: 0,
+ });
+ // Every other page, by offset, at the same page size, and none of them twice.
+ expect(rest.map((one) => one.offset).sort((a, b) => a - b)).toEqual([
+ 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000,
+ ]);
+ for (const one of rest) expect(one.limit).toBe(1000);
+ // ...all asked for AFTER page one answered, and BEFORE any of them answered: one wave.
+ for (const one of rest) expect(one.answeredBefore).toBe(1);
+ expect(e.maxInFlight()).toBe(8);
+ });
+
+ test("a bigger directory is still fetched at most eight pages at a time", async () => {
+ // 20,000 tools: 19 pages after the first, so the bound — not the directory — decides the peak.
+ const e = engine({ claimed: 20_000, latency: { small: 5, large: 20 } });
+
+ const apps = await browse();
+
+ expect(apps.reduce((n, app) => n + app.actionCount, 0)).toBe(20_000);
+ expect(e.asked.length).toBe(21);
+ expect(e.maxInFlight()).toBe(8);
+ });
+});
+
+describe("fetching ahead does not weaken the incompleteness check", () => {
+ test("a directory that claims 8,320 and serves 8,000 is refused", async () => {
+ engine({ claimed: DIRECTORY, served: 8000 });
+
+ await expect(browse()).rejects.toThrow(
+ /listed 8000 of 8320 actions, so its app catalogue is incomplete/,
+ );
+ });
+
+ test("a short page in the MIDDLE ends the listing, and the pages fetched past it are not counted", async () => {
+ // The page at offset 4000 holds 500 rows; the five pages fetched ahead past it hold nothing.
+ // A reader that summed every answer would still see 4500 — but one that kept reading past the
+ // short page would be trusting a listing that already said it was over.
+ engine({ claimed: DIRECTORY, served: 4500 });
+
+ await expect(browse()).rejects.toThrow(
+ /listed 4500 of 8320 actions, so its app catalogue is incomplete/,
+ );
+ });
+
+ test("a total that UNDER-reports is read on past, sequentially, to the real end", async () => {
+ // Claims 2000, serves 3500: the prefetch covers offsets 1000 and 2000, both full, so the
+ // listing must go on asking — a short page ends it, never the total.
+ const e = engine({
+ claimed: 2000,
+ served: 3500,
+ latency: { small: 5, large: 20 },
+ });
+
+ const apps = await browse();
+
+ expect(apps.reduce((n, app) => n + app.actionCount, 0)).toBe(3500);
+ expect(e.asked.map((one) => one.offset).sort((a, b) => a - b)).toEqual([
+ 0, 1000, 2000, 3000,
+ ]);
+ });
+});
diff --git a/server/tests/arcade-disconnect-cascade.test.ts b/server/tests/arcade-disconnect-cascade.test.ts
new file mode 100644
index 000000000..e0ad48e5e
--- /dev/null
+++ b/server/tests/arcade-disconnect-cascade.test.ts
@@ -0,0 +1,281 @@
+// server/tests/arcade-disconnect-cascade.test.ts
+import { afterAll, afterEach, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { and, asc, eq, inArray, sql } from "drizzle-orm";
+import { createAuditStore } from "../src/audit";
+import { createDatabase } from "../src/db/client";
+import { auditEvents, brokeredConnections, mcpServers } from "../src/db/schema";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import { createPluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+
+/* The pool this file opened, given back. `bun test` runs every file in one process and nothing
+ * reaps an idle connection, so a pool held to the end of the run is two connections the rest of
+ * the suite cannot have. */
+afterAll(async () => {
+ await database.$client.close();
+});
+const suite = randomUUID().slice(0, 8);
+const gmail = `Gmail-${suite}`;
+const calendar = `GoogleCalendar-${suite}`;
+const slack = `Slack-${suite}`;
+const drive = `GoogleDrive-${suite}`;
+const lonely = `Lonely-${suite}`;
+const keyed = `Keyed-${suite}`;
+
+const providerOf: Record = {
+ [gmail]: "google",
+ [calendar]: "google",
+ [drive]: null, // the resolver cannot answer for this one
+ [slack]: "slack",
+ [lonely]: "google",
+ // Gmail's connection exactly, except that this deployment holds a KEY for it.
+ [keyed]: "google",
+};
+
+/** What the vendor was asked to revoke, in order, and whether it should refuse. */
+const revoked: string[] = [];
+let refuseRevoke = false;
+/*
+ * What `revoke` ANSWERS where it does not refuse. `false` is "there was nothing to withdraw", and
+ * it is the answer no app but the asked one may be claimed gone on.
+ */
+let revokeAnswer = true;
+
+const broker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ authorizations: {
+ status: async () => {
+ throw new Error("nothing here polls an authorization");
+ },
+ confirmReturn: async () => {
+ throw new Error("nothing here confirms a return leg");
+ },
+ },
+ authProviderOf: async (toolkit) => providerOf[toolkit] ?? null,
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async () => ({
+ redirectUrl: "https://vendor.example/consent",
+ authorizationId: "ar_1",
+ }),
+ isConnected: async () => true,
+ revoke: async ({ toolkit }) => {
+ revoked.push(toolkit);
+ if (refuseRevoke)
+ throw new Error("Arcade revoke failed for 1 of 2 connections.");
+ return revokeAnswer;
+ },
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => "acme",
+};
+
+const store = createPluginStore({
+ brokerProvider: "arcade",
+ database,
+ auditStore: createAuditStore(database),
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce" as const, deny: [], allow: ["true"] }),
+ broker,
+});
+
+const seeded: string[] = [];
+async function hold(app: string, userId: string) {
+ seeded.push(app);
+ await database.insert(brokeredConnections).values({
+ provider: "arcade",
+ app,
+ holder: "person",
+ userId,
+ vendorUserId: userId,
+ });
+}
+/*
+ * A row under the DEPLOYMENT's holder but on a person's vendor identity: `resolveSiblings` reads
+ * the identity and so finds it, and `accountRow` reads the holder and so does not touch it.
+ */
+async function holdForDeployment(app: string, vendorUserId: string) {
+ seeded.push(app);
+ await database.insert(brokeredConnections).values({
+ provider: "arcade",
+ app,
+ holder: "deployment",
+ userId: null,
+ vendorUserId,
+ });
+}
+/*
+ * The app's own row, which is where its auth scheme is recorded and so the only thing that says
+ * whether this deployment holds a key for it.
+ */
+const servers: string[] = [];
+async function server(app: string, authScheme: string) {
+ servers.push(app);
+ await database.insert(mcpServers).values({
+ id: app,
+ title: app,
+ vendor: "Arcade",
+ url: `arcade://${app}`,
+ provenance: "arcade",
+ authScheme,
+ });
+}
+const rowsFor = async (apps: string[]) =>
+ (
+ await database
+ .select({ app: brokeredConnections.app })
+ .from(brokeredConnections)
+ .where(inArray(brokeredConnections.app, apps))
+ .orderBy(asc(brokeredConnections.app))
+ ).map((row) => row.app);
+// Filed by owner, because the trail is append-only and an earlier test's rows are still there.
+const trailFor = (userId: string) =>
+ database
+ .select({
+ app: auditEvents.targetId,
+ payload: sql>`payload`,
+ })
+ .from(auditEvents)
+ .where(
+ and(
+ eq(auditEvents.eventType, "mcp.account_disconnected"),
+ sql`payload ->> 'owner' = ${userId}`,
+ ),
+ )
+ .orderBy(asc(auditEvents.targetId));
+
+// The audit trail is append-only, so it is never cleaned: every name here carries this run's suffix.
+afterEach(async () => {
+ if (seeded.length) {
+ await database
+ .delete(brokeredConnections)
+ .where(inArray(brokeredConnections.app, seeded));
+ }
+ seeded.length = 0;
+ if (servers.length) {
+ await database.delete(mcpServers).where(inArray(mcpServers.id, servers));
+ }
+ servers.length = 0;
+ revoked.length = 0;
+ refuseRevoke = false;
+ revokeAnswer = true;
+});
+
+const disconnect = (userId: string, toolkit: string) =>
+ store.disconnectBrokered({
+ toolkit,
+ account: { holder: "person", userId, vendorUserId: userId },
+ by: userId,
+ reason: "self",
+ });
+
+describe("the disconnect cascade", () => {
+ test("a disconnect whose siblings are two apps removes and audits BOTH local rows", async () => {
+ const userId = `person-${suite}-a`;
+ await hold(gmail, userId);
+ await hold(calendar, userId);
+ await hold(slack, userId); // another provider: untouched
+
+ await disconnect(userId, gmail);
+
+ expect(await rowsFor([gmail, calendar, slack])).toEqual([slack]);
+ const trail = await trailFor(userId);
+ expect(trail.map((event) => event.app)).toEqual([calendar, gmail].sort());
+ });
+
+ test("a partial vendor failure THROWS and every local row survives, with no audit row", async () => {
+ const userId = `person-${suite}-b`;
+ await hold(gmail, userId);
+ await hold(calendar, userId);
+ refuseRevoke = true;
+
+ await expect(disconnect(userId, gmail)).rejects.toThrow(/revoke failed/);
+
+ expect(await rowsFor([gmail, calendar])).toEqual([calendar, gmail].sort());
+ expect(await trailFor(userId)).toEqual([]);
+ });
+
+ test("a disconnect with no siblings removes and audits exactly the one app", async () => {
+ const userId = `person-${suite}-c`;
+ await hold(lonely, userId);
+ await hold(slack, userId);
+
+ await disconnect(userId, lonely);
+
+ expect(await rowsFor([lonely, slack])).toEqual([slack]);
+ const trail = await trailFor(userId);
+ expect(trail.map((event) => event.app)).toEqual([lonely]);
+ });
+
+ test("the audit payload carries the unanswered count, an integer and never names", async () => {
+ const userId = `person-${suite}-d`;
+ await hold(gmail, userId);
+ await hold(calendar, userId);
+ await hold(drive, userId); // its auth provider cannot be resolved
+
+ await disconnect(userId, gmail);
+
+ // The unanswered app is NOT touched (left out, never guessed)...
+ expect(await rowsFor([gmail, calendar, drive])).toEqual([drive]);
+ // ...and every row written says that one app was not known.
+ const trail = await trailFor(userId);
+ expect(trail).toHaveLength(2);
+ for (const event of trail) {
+ expect(event.payload.unansweredSiblings).toBe(1);
+ expect(JSON.stringify(event.payload)).not.toContain(drive);
+ }
+ });
+
+ /*
+ * THE THREE RULES A CASCADE DELETE OBEYS, each asserted on the thing they protect: the row of an
+ * app NOBODY ASKED ABOUT. All three live in `clearEndedSiblings` in `store.ts`.
+ */
+ test("a KEY sibling's row SURVIVES, and nothing is written about it", async () => {
+ const userId = `person-${suite}-e`;
+ await server(keyed, "API_KEY");
+ await hold(gmail, userId);
+ await hold(keyed, userId); // same identity, same provider — but a key, not a consent
+
+ await disconnect(userId, gmail);
+
+ // The one OAuth revoke never ended the key, so the row that is the only way to reach that
+ // account is still here, and the trail claims nothing about it.
+ expect(await rowsFor([gmail, keyed])).toEqual([keyed]);
+ expect((await trailFor(userId)).map((event) => event.app)).toEqual([gmail]);
+ });
+
+ test("a revoke that ANSWERS FALSE leaves a sibling's row standing and unaudited", async () => {
+ const userId = `person-${suite}-f`;
+ await hold(gmail, userId);
+ await hold(calendar, userId);
+ revokeAnswer = false;
+
+ await disconnect(userId, gmail);
+
+ // Nothing was shown to have been withdrawn, so no app but the asked one is claimed gone.
+ expect(await rowsFor([gmail, calendar])).toEqual([calendar]);
+ const trail = await trailFor(userId);
+ expect(trail.map((event) => event.app)).toEqual([gmail]);
+ expect(trail[0]?.payload.vendorRevocationRequested).toBe(false);
+ });
+
+ test("a sibling this account holds NO ROW for gets no trail row", async () => {
+ const userId = `person-${suite}-g`;
+ await hold(gmail, userId);
+ await holdForDeployment(calendar, userId); // a sibling, but not this account's row
+
+ await disconnect(userId, gmail);
+
+ // The row is another holder's and so was never this disconnect's to delete — and a row that
+ // was not deleted has no vanished connection to explain.
+ expect(await rowsFor([gmail, calendar])).toEqual([calendar]);
+ expect((await trailFor(userId)).map((event) => event.app)).toEqual([gmail]);
+ });
+});
diff --git a/server/tests/arcade-disconnect-route.test.ts b/server/tests/arcade-disconnect-route.test.ts
new file mode 100644
index 000000000..f587bd35d
--- /dev/null
+++ b/server/tests/arcade-disconnect-route.test.ts
@@ -0,0 +1,331 @@
+// server/tests/arcade-disconnect-route.test.ts
+/**
+ * `GET /api/plugins/servers/:id/connection/siblings` and `DELETE /api/plugins/servers/:id/connection`,
+ * driven through `createApp`: what ending one app's connection ends besides it, and the cascade.
+ *
+ * THE ROWS AND THE TRAIL ARE ASSERTED, never a response body alone.
+ *
+ * SCOPING. `audit_events` is append-only and nothing here is cleaned from it, so every case mints
+ * its own `run()`: a fresh `suite+counter` suffix on every app, person and vendor identity it
+ * touches. Rows and trail are read through `inArray` on THAT case's app names only, and the
+ * "untouched" and "nothing audited" assertions therefore name keys no other case writes — an
+ * unwanted deletion or audit row for one of them could only have come from this case.
+ */
+import { afterAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { and, asc, eq, inArray } from "drizzle-orm";
+import { createApp } from "../src/app";
+import { createAuditStore } from "../src/audit";
+import { loadConfig } from "../src/config";
+import { createDatabase } from "../src/db/client";
+import { auditEvents, brokeredConnections, mcpServers } from "../src/db/schema";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import { createPluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { testEnvironment } from "./support/environment";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+let counter = 0;
+
+type Provider = "arcade" | "composio";
+let revokes: string[] = [];
+/**
+ * What the vendor stub answers. A function answers PER TOOLKIT, which is the only way a case can
+ * have one connection's revoke succeed while another's fails: the store makes exactly one ask per
+ * disconnect, so a partial failure is a wave of disconnects and not a single call that half-ran.
+ */
+type RevokeAnswer = boolean | Error | ((toolkit: string) => boolean | Error);
+let revokeAnswer: RevokeAnswer = true;
+
+const providerOf = (provider: Provider) => (toolkit: string) =>
+ provider === "composio"
+ ? toolkit
+ : toolkit.startsWith("Slack")
+ ? "slack"
+ : "google";
+
+function brokerFor(provider: Provider): ConnectedAppBroker {
+ return {
+ grantIsLocal: false,
+ authProviderOf: async (toolkit) => providerOf(provider)(toolkit),
+ authorizations: null,
+ grantsFor: async () => [],
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async () => ({
+ redirectUrl: "https://vendor.example/consent",
+ authorizationId: null,
+ }),
+ isConnected: async () => true,
+ revoke: async (request) => {
+ revokes.push(request.toolkit);
+ const answer =
+ typeof revokeAnswer === "function"
+ ? revokeAnswer(request.toolkit)
+ : revokeAnswer;
+ if (answer instanceof Error) throw answer;
+ return answer;
+ },
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => null,
+ };
+}
+
+function build(who: string, provider: Provider) {
+ const broker = brokerFor(provider);
+ const auditStore = createAuditStore(database);
+ const pluginStore = createPluginStore({
+ brokerProvider: provider,
+ database,
+ auditStore,
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }),
+ broker,
+ deploymentId: `acme-${suite}`,
+ });
+ const args = new Array(createApp.length) as unknown as Parameters<
+ typeof createApp
+ >;
+ args[0] = loadConfig(
+ testEnvironment({ OPENBOT_APP_URL: "https://app.example" }),
+ );
+ args[1] = {
+ handler: () => new Response(null, { status: 204 }),
+ api: {
+ getSession: async () => ({
+ user: { id: who, email: `${who}@example.test`, name: "Person" },
+ }),
+ },
+ } as never;
+ args[2] = { rolesForUser: async () => ["user"] } as never;
+ args[12] = auditStore;
+ args[14] = pluginStore;
+ args[29] = { broker, provider };
+ return createApp(...args);
+}
+
+const toolkits: string[] = [];
+
+/** This case's own names. Nothing another case writes can share one. */
+function run(provider: Provider = "arcade") {
+ counter += 1;
+ const tag = `${suite}${counter}`;
+ const names = {
+ gmail: `Gmail${tag}`,
+ calendar: `GoogleCalendar${tag}`,
+ docs: `GoogleDocs${tag}`,
+ slack: `Slack${tag}`,
+ person: `person-${tag}`,
+ shared: `openbot-deployment:acme:${tag}`,
+ };
+ toolkits.push(names.gmail, names.calendar, names.docs, names.slack);
+ revokes = [];
+ revokeAnswer = true;
+ return { ...names, provider, id: (toolkit: string) => `srv-${toolkit}` };
+}
+
+async function app(n: ReturnType, toolkit: string) {
+ await database.insert(mcpServers).values({
+ id: n.id(toolkit),
+ title: toolkit,
+ vendor: n.provider === "arcade" ? "Arcade" : "Composio",
+ url: `${n.provider}://${toolkit}`,
+ provenance: n.provider,
+ authScheme: "OAUTH2",
+ accountMode: "personal",
+ });
+}
+async function hold(
+ n: ReturnType,
+ toolkit: string,
+ vendorUserId: string,
+ holder: "person" | "deployment" = "person",
+) {
+ await database.insert(brokeredConnections).values({
+ provider: n.provider,
+ app: toolkit,
+ holder,
+ userId: holder === "person" ? vendorUserId : null,
+ vendorUserId,
+ });
+}
+const rowsFor = async (names: string[]) =>
+ (
+ await database
+ .select({ app: brokeredConnections.app })
+ .from(brokeredConnections)
+ .where(inArray(brokeredConnections.app, names))
+ .orderBy(asc(brokeredConnections.app))
+ ).map((row) => row.app);
+const trailFor = async (names: string[]) =>
+ (
+ await database
+ .select({ app: auditEvents.targetId })
+ .from(auditEvents)
+ .where(
+ and(
+ eq(auditEvents.eventType, "mcp.account_disconnected"),
+ inArray(auditEvents.targetId, names),
+ ),
+ )
+ .orderBy(asc(auditEvents.targetId))
+ ).map((row) => row.app);
+
+const preview = (n: ReturnType, toolkit: string) =>
+ build(n.person, n.provider).request(
+ `http://openbot.test/api/plugins/servers/${n.id(toolkit)}/connection/siblings`,
+ );
+const disconnect = (n: ReturnType, toolkit: string) =>
+ build(n.person, n.provider).request(
+ `http://openbot.test/api/plugins/servers/${n.id(toolkit)}/connection`,
+ { method: "DELETE" },
+ );
+
+afterAll(async () => {
+ await database
+ .delete(brokeredConnections)
+ .where(inArray(brokeredConnections.app, toolkits));
+ await database.delete(mcpServers).where(
+ inArray(
+ mcpServers.id,
+ toolkits.map((t) => `srv-${t}`),
+ ),
+ );
+ await database.$client.close();
+});
+
+describe("the sibling list the confirmation shows", () => {
+ test("is computed on the PAIR: another identity's app on the same provider is not named", async () => {
+ const n = run();
+ for (const t of [n.gmail, n.calendar, n.docs, n.slack]) await app(n, t);
+ await hold(n, n.gmail, n.person);
+ await hold(n, n.calendar, n.person); // same identity, same provider: a sibling
+ await hold(n, n.docs, n.shared, "deployment"); // same provider, ANOTHER identity
+ await hold(n, n.slack, n.person); // same identity, ANOTHER provider
+
+ const response = await preview(n, n.gmail);
+
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({
+ siblings: [n.calendar],
+ unanswered: 0,
+ });
+ // Asking changes nothing: no revoke, no row gone.
+ expect(revokes).toEqual([]);
+ expect(await rowsFor([n.gmail, n.calendar, n.docs, n.slack])).toEqual(
+ [n.gmail, n.calendar, n.docs, n.slack].sort(),
+ );
+ });
+});
+
+describe("the disconnect", () => {
+ test("removes and audits every app that ended, and nothing on another pair", async () => {
+ const n = run();
+ for (const t of [n.gmail, n.calendar, n.docs, n.slack]) await app(n, t);
+ await hold(n, n.gmail, n.person);
+ await hold(n, n.calendar, n.person);
+ await hold(n, n.docs, n.shared, "deployment");
+ await hold(n, n.slack, n.person);
+
+ const response = await disconnect(n, n.gmail);
+
+ expect(response.status).toBe(200);
+ expect(revokes).toEqual([n.gmail]); // ONE ask to the vendor
+ expect(await rowsFor([n.gmail, n.calendar, n.docs, n.slack])).toEqual(
+ [n.docs, n.slack].sort(),
+ );
+ expect(await trailFor([n.gmail, n.calendar, n.docs, n.slack])).toEqual(
+ [n.gmail, n.calendar].sort(),
+ );
+ });
+
+ test("a revoke that throws leaves EVERY row of the identity standing and audits nothing", async () => {
+ const n = run();
+ // Three apps on ONE auth provider, so the single ask below covers two siblings as well as the
+ // app pressed: the throw must leave all three, not only the one named.
+ for (const t of [n.gmail, n.calendar, n.docs]) await app(n, t);
+ await hold(n, n.gmail, n.person);
+ await hold(n, n.calendar, n.person);
+ await hold(n, n.docs, n.person);
+ revokeAnswer = new Error("revoke failed halfway");
+
+ const response = await disconnect(n, n.gmail);
+
+ expect(response.status).toBeGreaterThanOrEqual(500);
+ expect(revokes).toEqual([n.gmail]);
+ expect(await rowsFor([n.gmail, n.calendar, n.docs])).toEqual(
+ [n.gmail, n.calendar, n.docs].sort(),
+ );
+ expect(await trailFor([n.gmail, n.calendar, n.docs])).toEqual([]);
+ });
+
+ test("a partial failure across an identity's connections ends the asked one and leaves the failed one whole", async () => {
+ const n = run();
+ // TWO connections for one identity: Slack is its own auth provider, Gmail and Calendar share
+ // google. The store asks the vendor ONCE per disconnect, so "partial" is this wave — one ask
+ // answered, the next thrown on — and the point is that the succeeded half is not rolled back
+ // and the failed half is not half-applied.
+ for (const t of [n.gmail, n.calendar, n.slack]) await app(n, t);
+ await hold(n, n.gmail, n.person);
+ await hold(n, n.calendar, n.person);
+ await hold(n, n.slack, n.person);
+ revokeAnswer = (toolkit) =>
+ toolkit === n.slack ? true : new Error("revoke failed halfway");
+
+ const ok = await disconnect(n, n.slack);
+ const failed = await disconnect(n, n.gmail);
+
+ expect(ok.status).toBe(200);
+ expect(await ok.json()).toEqual({ vendorRevocationRequested: true });
+ expect(failed.status).toBeGreaterThanOrEqual(500);
+ // BOTH asks reached the vendor, in that order: the failure is the vendor's answer and not a
+ // disconnect this deployment declined to make.
+ expect(revokes).toEqual([n.slack, n.gmail]);
+ // The succeeded one is gone; the failed one took NEITHER its own row nor its sibling's.
+ expect(await rowsFor([n.gmail, n.calendar, n.slack])).toEqual(
+ [n.gmail, n.calendar].sort(),
+ );
+ expect(await trailFor([n.gmail, n.calendar, n.slack])).toEqual([n.slack]);
+ });
+
+ test("answers what the vendor was asked, passing a false through unrewritten", async () => {
+ const n = run();
+ await app(n, n.gmail);
+ await hold(n, n.gmail, n.person);
+ revokeAnswer = false;
+
+ const response = await disconnect(n, n.gmail);
+
+ expect(await response.json()).toEqual({ vendorRevocationRequested: false });
+ // The false is the VENDOR's: one DELETE was spent asking, not a default returned by a route
+ // that deleted the row and never asked.
+ expect(revokes).toEqual([n.gmail]);
+ expect(await rowsFor([n.gmail])).toEqual([]);
+ });
+});
+
+describe("Composio, where an app is its own auth provider", () => {
+ test("names no siblings and disconnects exactly the one app", async () => {
+ const n = run("composio");
+ for (const t of [n.gmail, n.calendar]) await app(n, t);
+ await hold(n, n.gmail, n.person);
+ await hold(n, n.calendar, n.person);
+
+ const listed = await preview(n, n.gmail);
+ expect(await listed.json()).toEqual({ siblings: [], unanswered: 0 });
+
+ const response = await disconnect(n, n.gmail);
+
+ expect(response.status).toBe(200);
+ // ONE DELETE spent at the vendor, for the app pressed and no other: the local row going is
+ // half the disconnect, and a row deleted with no ask leaves a live grant nothing here reaches.
+ expect(revokes).toEqual([n.gmail]);
+ expect(await rowsFor([n.gmail, n.calendar])).toEqual([n.calendar]);
+ expect(await trailFor([n.gmail, n.calendar])).toEqual([n.gmail]);
+ });
+});
diff --git a/server/tests/arcade-empty-vendor-structures-engine.test.ts b/server/tests/arcade-empty-vendor-structures-engine.test.ts
new file mode 100644
index 000000000..f2f6b4bde
--- /dev/null
+++ b/server/tests/arcade-empty-vendor-structures-engine.test.ts
@@ -0,0 +1,280 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { listTools, useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+
+/**
+ * AN EMPTY VENDOR STRUCTURE IS NOT A VENDOR ANSWER, AGAINST A REAL HTTP ENGINE.
+ *
+ * `metadata: { behavior: {} }` and `requirements: { secrets: {} }` are containers Arcade opened and
+ * put nothing in. Read as answers they say "not destructive" and "nothing is required" — a
+ * permission decision taken on missing data, and the opposite of what an ABSENT `metadata` or an
+ * absent `behavior` already answers two lines away in the same function.
+ *
+ * SERVED OVER A REAL SOCKET, as `arcade-pagination-engine.test.ts` and
+ * `arcade-malformed-listing-engine.test.ts` are and for their reason: the rest of the adapter suite
+ * stubs the SDK's resource PROTOTYPES and so decides for itself what the adapter is handed, which
+ * cannot see anything that happens between the wire and the reader. Here the SDK parses the body
+ * and builds its own pages, so what these assertions read is what a deployment would.
+ */
+
+const account: AccountRef = {
+ holder: "person",
+ userId: "u1",
+ vendorUserId: "vendor-u1",
+};
+
+type Engine = { port: number; stop: () => void };
+
+/** The rows `/v1/tools` serves, set per test. One short page ends the listing. */
+let serving: unknown[] = [];
+
+function startEngine(): Engine {
+ const server = Bun.serve({
+ port: 0,
+ fetch(request) {
+ const url = new URL(request.url);
+ const limit = Number(url.searchParams.get("limit") ?? "100");
+ const offset = Number(url.searchParams.get("offset") ?? "0");
+ if (url.pathname === "/v1/tools") {
+ const page = serving.slice(offset, offset + limit);
+ return Response.json({
+ items: page,
+ offset,
+ limit,
+ page_count: page.length,
+ });
+ }
+ return new Response("not found", { status: 404 });
+ },
+ });
+ return { port: server.port, stop: () => server.stop(true) };
+}
+
+let running: Engine | null = null;
+const wasBaseURL = process.env.ARCADE_BASE_URL;
+
+function engine(rows: unknown[]): void {
+ serving = rows;
+ running = startEngine();
+ process.env.ARCADE_BASE_URL = `http://localhost:${running.port}`;
+}
+
+afterEach(() => {
+ running?.stop();
+ running = null;
+ serving = [];
+ useArcadeClient(null);
+ if (wasBaseURL === undefined) delete process.env.ARCADE_BASE_URL;
+ else process.env.ARCADE_BASE_URL = wasBaseURL;
+});
+
+/** One listed action, with whatever `metadata` and `requirements` the case is about. */
+const toolRow = (
+ name: string,
+ metadata: unknown,
+ requirements: unknown = {
+ authorization: {
+ provider_id: "google",
+ oauth2: { scopes: [] },
+ token_status: "completed",
+ },
+ },
+) => ({
+ qualified_name: `Gmail.${name}`,
+ description: "",
+ toolkit: { name: "Gmail", description: "" },
+ input: { parameters: [] },
+ metadata,
+ requirements,
+});
+
+const listed = async () => {
+ const { actions } = createArcadeClient("never-dialled");
+ useArcadeClient(actions);
+ return await listTools({ url: "arcade://Gmail" });
+};
+
+describe("an empty behavior object is the vendor saying nothing, not saying safe", () => {
+ test("a behavior object stating neither flag is read as the closed verdict", async () => {
+ engine([toolRow("EmptyBehavior", { behavior: {} })]);
+
+ expect(await listed()).toMatchObject([
+ {
+ name: "EmptyBehavior",
+ vendorName: "Gmail.EmptyBehavior",
+ effect: "write",
+ destructive: true,
+ },
+ ]);
+ });
+
+ test("a behavior object carrying only unrelated keys says nothing either", async () => {
+ engine([toolRow("Idempotent", { behavior: { idempotent: true } })]);
+
+ expect(await listed()).toMatchObject([
+ {
+ name: "Idempotent",
+ vendorName: "Gmail.Idempotent",
+ effect: "write",
+ destructive: true,
+ },
+ ]);
+ });
+
+ test("an empty behavior does not count towards classifiedCount", async () => {
+ engine([
+ toolRow("EmptyBehavior", { behavior: {} }),
+ toolRow("Annotated", {
+ behavior: { read_only: true, destructive: false },
+ }),
+ ]);
+ const { broker } = createArcadeClient("never-dialled");
+
+ const apps = await broker.listApps();
+ expect(apps.length).toBe(1);
+ expect(apps[0]?.actionCount).toBe(2);
+ // ONE of the two: the empty container is an action Arcade has not classified, which is exactly
+ // what the coverage warning exists to name.
+ expect(apps[0]?.classifiedCount).toBe(1);
+ });
+
+ test("a stated behaviour is still read, in both directions", async () => {
+ engine([
+ toolRow("Read", { behavior: { read_only: true } }),
+ toolRow("Write", { behavior: { destructive: false, read_only: false } }),
+ toolRow("Delete", { behavior: { destructive: true } }),
+ ]);
+
+ expect(await listed()).toMatchObject([
+ {
+ name: "Read",
+ vendorName: "Gmail.Read",
+ effect: "read",
+ destructive: false,
+ },
+ {
+ name: "Write",
+ vendorName: "Gmail.Write",
+ effect: "write",
+ destructive: false,
+ },
+ {
+ name: "Delete",
+ vendorName: "Gmail.Delete",
+ effect: "write",
+ destructive: true,
+ },
+ ]);
+ });
+});
+
+describe("a secrets requirement that is not a list is unreadable, not satisfied", () => {
+ const withSecrets = (secrets: unknown) =>
+ toolRow(
+ "Send",
+ { behavior: { read_only: true } },
+ {
+ secrets,
+ authorization: {
+ provider_id: "google",
+ oauth2: { scopes: [] },
+ token_status: "completed",
+ },
+ },
+ );
+
+ test("an empty secrets object does not answer authorized", async () => {
+ engine([withSecrets({})]);
+ const { broker } = createArcadeClient("never-dialled");
+
+ expect(await broker.grantsFor({ account, toolkit: "Gmail" })).toEqual([
+ {
+ action: "Gmail.Send",
+ state: "unknown",
+ reason:
+ "Arcade listed this action's deployment secrets as something other than a list, so whether any is unset could not be read.",
+ },
+ ]);
+ });
+
+ test("no secrets field at all is still no secrets required", async () => {
+ engine([toolRow("Send", { behavior: { read_only: true } })]);
+ const { broker } = createArcadeClient("never-dialled");
+
+ expect(await broker.grantsFor({ account, toolkit: "Gmail" })).toEqual([
+ {
+ action: "Gmail.Send",
+ state: "authorized",
+ scopes: [],
+ authorization: "held",
+ },
+ ]);
+ });
+
+ test("a listed secret is still read, met or unset", async () => {
+ engine([withSecrets([{ key: "GITHUB_SERVER_URL", met: false }])]);
+ const { broker } = createArcadeClient("never-dialled");
+
+ expect(await broker.grantsFor({ account, toolkit: "Gmail" })).toEqual([
+ {
+ action: "Gmail.Send",
+ state: "needs-operator",
+ missing: ["GITHUB_SERVER_URL"],
+ },
+ ]);
+ });
+});
+
+/**
+ * THE DEFECT: EVERY ARCADE ACTION WAS UNCALLABLE BY EVERY MODEL, AND NOTHING SAID SO.
+ *
+ * Arcade's listing answers `qualified_name` — `Gmail.ListEmails` — and `mcp_tools.name` used to be
+ * recorded as exactly that. `toolNameFor` then spliced it into `mcp__arcade-Gmail__Gmail.ListEmails`
+ * and the model API refused the request: a tool name may only match `^[a-zA-Z0-9_-]+$`, and the dot
+ * does not. Because a request carries EVERY tool together, one granted Arcade action failed the
+ * WHOLE call — `Invalid 'tools[47].name'` — so a Bot holding one Arcade tool could not answer
+ * anything at all.
+ *
+ * It survived every test in this tree because no test ever asked whether the name it recorded was a
+ * name a model could be offered. These do.
+ */
+describe("an Arcade action is recorded under a name a model may be offered", () => {
+ test("the toolkit prefix is dropped, and the vendor's own spelling is kept beside it", async () => {
+ engine([toolRow("ListEmails", { behavior: { read: true } })]);
+
+ expect(await listed()).toMatchObject([
+ { name: "ListEmails", vendorName: "Gmail.ListEmails" },
+ ]);
+ });
+
+ test("every listed name is one the model API accepts", async () => {
+ engine([
+ toolRow("ListEmails", null),
+ toolRow("SendEmail", null),
+ toolRow("TrashEmail", null),
+ ]);
+
+ /*
+ * THE PATTERN ITSELF, not a sample of names somebody thought of. This is the whole of what the
+ * model API will take, and `name` is spliced into a tool name unchanged — so a listing that
+ * passes here is a request that is not refused for its names.
+ */
+ for (const tool of await listed()) {
+ expect(tool.name).toMatch(/^[a-zA-Z0-9_-]+$/);
+ }
+ });
+
+ test("a qualified name that is not this toolkit's is left whole", async () => {
+ /*
+ * ONLY THE VENDOR'S OWN PREFIX IS REMOVED. A listing that qualifies an action with some other
+ * name is not something to repair by guessing: the name is kept as it stands and the guard in
+ * `storableTools` is what refuses it, by name, where it has a provenance.
+ */
+ engine([{ ...toolRow("Other", null), qualified_name: "Elsewhere.Other" }]);
+
+ expect(await listed()).toMatchObject([
+ { name: "Elsewhere.Other", vendorName: "Elsewhere.Other" },
+ ]);
+ });
+});
diff --git a/server/tests/arcade-enablement-facts.test.ts b/server/tests/arcade-enablement-facts.test.ts
new file mode 100644
index 000000000..75b854d8c
--- /dev/null
+++ b/server/tests/arcade-enablement-facts.test.ts
@@ -0,0 +1,171 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import Arcade from "@arcadeai/arcadejs";
+import fixture from "./fixtures/arcade-catalogue.json";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { BrokerApp } from "../src/plugins/broker";
+
+/**
+ * The three enablement facts `listApps` measures beside `actionCount`: how many actions Arcade
+ * classified, how many distinct non-empty scope sets they ask for, and how many carry a scope.
+ *
+ * MEASURED INSIDE THE EXISTING FOLD. `listApps` already pages `tools.list` to the end and folds
+ * every tool into a per-toolkit `actionCount` before discarding the tools; these three are counted
+ * before that discard. No second listing, no extra vendor call — which `asked` asserts below.
+ */
+type Methods = Record unknown>;
+const toolsProto = (): Methods =>
+ Object.getPrototypeOf(
+ new Arcade({ apiKey: "never-dialled" }).tools,
+ ) as Methods;
+
+const restores: (() => void)[] = [];
+function stub(
+ on: Methods,
+ name: string,
+ impl: (...args: unknown[]) => unknown,
+) {
+ const was = on[name];
+ on[name] = impl;
+ restores.push(() => {
+ on[name] = was;
+ });
+}
+afterEach(() => {
+ while (restores.length) restores.pop()?.();
+});
+
+/** Serves one page of `items` and records every offset asked for. */
+function serving(items: unknown[]): { asked: number[] } {
+ const asked: number[] = [];
+ stub(toolsProto(), "list", (...args: unknown[]) => {
+ const q = args[0] as { offset: number };
+ asked.push(q.offset);
+ return Promise.resolve(
+ q.offset === 0
+ ? { items, total_count: items.length }
+ : { items: [], total_count: items.length },
+ );
+ });
+ return { asked };
+}
+
+const listed = async () =>
+ await createArcadeClient("never-dialled").broker.listApps();
+
+const appOf = (apps: BrokerApp[], slug: string): BrokerApp => {
+ const app = apps.find((a) => a.slug === slug);
+ if (!app) throw new Error(`no app ${slug} in the directory`);
+ return app;
+};
+
+/**
+ * THE FIXTURE IS EVIDENCE ABOUT WHAT ARCADE RETURNS, so it is read and never appended to. Its five
+ * tools are verbatim live shapes: three classified toolkits, one unclassified one, and inside that
+ * one the mixed case (one action with a scope, one with an empty `oauth2` block).
+ */
+const FIXTURE_TOOLS = Object.values(
+ fixture.tools as Record,
+) as unknown[];
+
+describe("listApps measures the enablement facts from the fixture", () => {
+ test("classifiedCount equals actionCount where Arcade published a behavior", async () => {
+ const { asked } = serving(FIXTURE_TOOLS);
+ const apps = await listed();
+ // No second listing: one short page ended it.
+ expect(asked).toEqual([0]);
+ for (const slug of ["Apollo", "Asana", "Attio"]) {
+ const app = appOf(apps, slug);
+ expect(app.classifiedCount).toBe(app.actionCount);
+ }
+ });
+
+ test("classifiedCount is 0 for a toolkit Arcade published no behavior for", async () => {
+ serving(FIXTURE_TOOLS);
+ const app = appOf(await listed(), "AirtableApi");
+ expect(app.actionCount).toBe(2);
+ // NEVER THE `Api` SUFFIX: the zero comes from `metadata: null` on both of its actions.
+ expect(app.classifiedCount).toBe(0);
+ });
+
+ test("permissionCount is 0 where no action carries an oauth2 block at all", async () => {
+ serving(FIXTURE_TOOLS);
+ const app = appOf(await listed(), "Apollo");
+ expect(app.permissionCount).toBe(0);
+ expect(app.scopedActionCount).toBe(0);
+ });
+
+ test("permissionCount is 1 for a toolkit whose actions share one scope set", async () => {
+ serving(FIXTURE_TOOLS);
+ const apps = await listed();
+ for (const slug of ["Asana", "Attio"]) {
+ const app = appOf(apps, slug);
+ expect(app.permissionCount).toBe(1);
+ expect(app.scopedActionCount).toBe(1);
+ expect(app.actionCount).toBe(1);
+ }
+ });
+
+ /**
+ * THE MIXED CASE, and the reason `scopedActionCount` exists. `AirtableApi` publishes
+ * `AddBaseCollaborator` with `workspacesAndBases:write` and `ListBases` with an EMPTY `oauth2`
+ * block. Without the third number this reads as one permission covering both actions; with it,
+ * the second is what it is — covered by any approval at all, and not separately approvable.
+ */
+ test("a scopeless action joins no permission group", async () => {
+ serving(FIXTURE_TOOLS);
+ const app = appOf(await listed(), "AirtableApi");
+ expect(app.actionCount).toBe(2);
+ expect(app.permissionCount).toBe(1);
+ expect(app.scopedActionCount).toBe(1);
+ });
+});
+
+/**
+ * `permissionCount > 1` IS A PURE COUNTING PROPERTY AND NEEDS NO REALISM, so it is constructed
+ * here rather than appended to the fixture. The fixture's value is that it is what Arcade actually
+ * returned; a second append makes that claim slightly less true while the file still carries it.
+ */
+describe("listApps counts distinct scope sets", () => {
+ const scoped = (name: string, scopes: string[]) => ({
+ qualified_name: `Constructed.${name}`,
+ toolkit: { name: "Constructed", description: "constructed" },
+ metadata: { behavior: { read_only: true, destructive: false } },
+ requirements: {
+ authorization: { provider_type: "oauth2", oauth2: { scopes } },
+ },
+ });
+
+ test("two distinct scope sets answer permissionCount 2", async () => {
+ const tools = [scoped("One", ["a", "b"]), scoped("Two", ["c"])];
+ serving(tools);
+ const app = appOf(await listed(), "Constructed");
+ expect(app.actionCount).toBe(2);
+ expect(app.permissionCount).toBe(2);
+ expect(app.scopedActionCount).toBe(2);
+ expect(app.classifiedCount).toBe(2);
+ });
+
+ /** SORTED BEFORE JOINED: `[a,b]` and `[b,a]` are the same permission, asked for twice. */
+ test("the same scopes in a different order are ONE permission", async () => {
+ const tools = [scoped("One", ["a", "b"]), scoped("Two", ["b", "a"])];
+ serving(tools);
+ const app = appOf(await listed(), "Constructed");
+ expect(app.actionCount).toBe(2);
+ expect(app.permissionCount).toBe(1);
+ expect(app.scopedActionCount).toBe(2);
+ });
+
+ /** Three actions, two sets, one of them reordered: the order never adds a permission. */
+ test("sorting does not collapse genuinely different sets", async () => {
+ const tools = [
+ scoped("One", ["a", "b"]),
+ scoped("Two", ["b", "a"]),
+ scoped("Three", ["a", "c"]),
+ ];
+ serving(tools);
+ const app = appOf(await listed(), "Constructed");
+ expect(app.actionCount).toBe(3);
+ expect(app.permissionCount).toBe(2);
+ expect(app.scopedActionCount).toBe(3);
+ });
+});
diff --git a/server/tests/arcade-errors.test.ts b/server/tests/arcade-errors.test.ts
new file mode 100644
index 000000000..d986c592b
--- /dev/null
+++ b/server/tests/arcade-errors.test.ts
@@ -0,0 +1,218 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import {
+ callTool,
+ isAuthorizationRequired,
+ listTools,
+ useArcadeClient,
+ type ArcadeFailure,
+} from "../src/plugins/arcade";
+import { MAX_RESULT_CHARS } from "../src/plugins/mcp";
+
+afterEach(() => useArcadeClient(null));
+
+const connection = { url: "arcade://Gmail", actorId: "u1" };
+const URL_IN_BODY = "https://github.com/login/oauth/authorize?state=SECRET";
+
+function failing(failure: unknown) {
+ useArcadeClient({
+ listTools: async () => {
+ throw new Error("not listing");
+ },
+ execute: async () => {
+ throw failure;
+ },
+ });
+}
+
+describe("isAuthorizationRequired", () => {
+ test("keys on the body's error name", () => {
+ expect(
+ isAuthorizationRequired({
+ status: 403,
+ name: "tool_authorization_required",
+ message: "authorization required",
+ }),
+ ).toBe(true);
+ });
+
+ test("the name alone is enough: it never reads the status", () => {
+ expect(
+ isAuthorizationRequired({
+ status: undefined,
+ name: "tool_authorization_required",
+ message: undefined,
+ }),
+ ).toBe(true);
+ });
+
+ test("a 403 is not an authorization prompt", () => {
+ for (const name of ["permission_denied", undefined, "rate_limited"]) {
+ expect(isAuthorizationRequired({ status: 403, name, message: "x" })).toBe(
+ false,
+ );
+ }
+ });
+});
+
+describe("callTool failures", () => {
+ test("an authorization-required failure names the action callTool was given", async () => {
+ failing({
+ status: 403,
+ name: "tool_authorization_required",
+ message: `authorization required ${URL_IN_BODY}`,
+ } satisfies ArcadeFailure);
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.isError).toBe(true);
+ expect(result.text).toBe(
+ "Gmail.SendEmail has not been authorized on this account yet. Authorize it in Settings → Connected accounts and ask again.",
+ );
+ expect(result.text).not.toContain("http");
+ expect(result.text).not.toContain("SECRET");
+ });
+
+ test("the action comes from the call site, not from anything on the error", async () => {
+ failing({
+ status: 403,
+ name: "tool_authorization_required",
+ message: "m",
+ toolName: "Other.Thing",
+ });
+ const result = await callTool(connection, "Slack.Post", {});
+ expect(result.text.startsWith("Slack.Post has not been authorized")).toBe(
+ true,
+ );
+ });
+
+ test("permission_denied at 403 is NOT an authorize prompt", async () => {
+ failing({ status: 403, name: "permission_denied", message: "key revoked" });
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.isError).toBe(true);
+ expect(result.text).not.toContain("has not been authorized");
+ expect(result.text).not.toContain("Connected accounts");
+ expect(result.text).not.toContain("http");
+ });
+
+ test("a throw that is not an ArcadeFailure is handled", async () => {
+ for (const thrown of [new Error("boom"), "string", null, undefined, 42]) {
+ failing(thrown);
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.isError).toBe(true);
+ expect(result.text).not.toContain("has not been authorized");
+ }
+ });
+});
+
+describe("listTools failures", () => {
+ test("a vendor error on the listing path does not leak the vendor's message", async () => {
+ const vendor = new Error(
+ '403 {"name":"permission_denied","message":"LEAKME key sk-123"}',
+ );
+ useArcadeClient({
+ listTools: async () => {
+ throw vendor;
+ },
+ execute: async () => {
+ throw new Error("no");
+ },
+ });
+ const error = await listTools(connection).then(
+ () => null,
+ (e: Error) => e,
+ );
+ expect(error).not.toBeNull();
+ expect(error!.message).not.toContain("LEAKME");
+ expect(error!.message).not.toContain("sk-123");
+ expect(error!.message).toContain("Gmail");
+ expect(error!.cause).toBe(vendor);
+ });
+});
+
+function answering(answer: unknown) {
+ useArcadeClient({
+ listTools: async () => {
+ throw new Error("not listing");
+ },
+ execute: async () => answer as never,
+ });
+}
+
+describe("callTool successes", () => {
+ test("a 200 carrying an authorization envelope leaks neither the url nor the token", async () => {
+ answering({
+ success: true,
+ output: {
+ value: { ok: 1 },
+ authorization: {
+ url: "https://example.test/auth?state=SECRET",
+ context: { token: "tok-LEAKME" },
+ },
+ },
+ });
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.text).not.toContain("http");
+ expect(result.text).not.toContain("SECRET");
+ expect(result.text).not.toContain("LEAKME");
+ expect(result.text).toBe(
+ "Gmail.SendEmail has not been authorized on this account yet. Authorize it in Settings → Connected accounts and ask again.",
+ );
+ expect(result.isError).toBe(true);
+ });
+
+ test("an Arcade-reported failure is an error, in this deployment's words", async () => {
+ answering({ success: false, output: { error: { message: "LEAKME" } } });
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.isError).toBe(true);
+ expect(result.text).toBe("Arcade could not run Gmail.SendEmail.");
+ expect(result.text).not.toContain("LEAKME");
+ });
+
+ test("a plain success returns output.value and nothing else from the envelope", async () => {
+ answering({
+ success: true,
+ execution_id: "exec-LEAKME",
+ duration: 12.5,
+ output: { value: { rows: [1, 2] } },
+ });
+ const result = await callTool(connection, "Gmail.SendEmail", {});
+ expect(result.isError).toBe(false);
+ expect(result.text).toBe(JSON.stringify({ rows: [1, 2] }, null, 2));
+ expect(result.text).not.toContain("execution_id");
+ expect(result.text).not.toContain("exec-LEAKME");
+ expect(result.text).not.toContain("duration");
+ });
+});
+
+/**
+ * A tool result is read by a model and copied into the audit row, so an unbounded one is the vendor
+ * — or whoever wrote the mailbox it read — deciding how much of our context window to spend and how
+ * large a durable record grows. `composio.ts`'s `cap()` is the convention this follows: BOTH answers
+ * go through it, the result and the refusal, and `truncated` is the field that says it happened.
+ */
+describe("callTool caps what a model is shown", () => {
+ test("an enormous result is cut and says it was cut", async () => {
+ const enormous = "x".repeat(MAX_RESULT_CHARS + 5_000);
+ answering({ success: true, output: { value: enormous } });
+ const result = await callTool(connection, "Gmail.ReadEmail", {});
+ expect(result.isError).toBe(false);
+ expect(result.truncated).toBe(true);
+ expect(result.text.length).toBeLessThan(enormous.length);
+ expect(result.text.endsWith("[truncated]")).toBe(true);
+ });
+
+ test("a result that fits is untouched and reports no truncation", async () => {
+ answering({ success: true, output: { value: { rows: [1, 2] } } });
+ const result = await callTool(connection, "Gmail.ReadEmail", {});
+ expect(result.truncated).toBe(false);
+ expect(result.text).toBe(JSON.stringify({ rows: [1, 2] }, null, 2));
+ });
+
+ test("a refusal is capped too: it carries a vendor-listed action name", async () => {
+ const enormousName = "Gmail.".padEnd(MAX_RESULT_CHARS + 2_000, "N");
+ answering({ success: false, output: { error: { message: "m" } } });
+ const result = await callTool(connection, enormousName, {});
+ expect(result.isError).toBe(true);
+ expect(result.truncated).toBe(true);
+ expect(result.text.length).toBeLessThan(enormousName.length);
+ expect(result.text.endsWith("[truncated]")).toBe(true);
+ });
+});
diff --git a/server/tests/arcade-grants-route.test.ts b/server/tests/arcade-grants-route.test.ts
new file mode 100644
index 000000000..53f07999a
--- /dev/null
+++ b/server/tests/arcade-grants-route.test.ts
@@ -0,0 +1,320 @@
+// server/tests/arcade-grants-route.test.ts
+/**
+ * `GET /api/plugins/servers/:id/grants`: what an account holds at an app, action by action.
+ *
+ * THE IDENTITY SENT UPSTREAM IS WHAT IS ASSERTED, never what rendered. The stub broker records the
+ * `account` each `grantsFor` call receives, and cases 1 and 2 read THAT. A route that asked about
+ * the viewer for a Shared app would render a perfectly plausible answer.
+ *
+ * SCOPING. `audit_events` is append-only and nothing here is cleaned between cases, so every case
+ * mints its own `run()`: a fresh `suite+counter` suffix on the toolkit, the app id, the viewer and
+ * the shared identity. The captured calls are filtered to THIS case's toolkit, and the "mint
+ * refused" check reads `audit_events` and `pending_authorizations` through `inArray` on this case's
+ * own names only, so a minted attempt by any other case could not satisfy or redden it.
+ */
+import { afterAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { inArray } from "drizzle-orm";
+import { createApp } from "../src/app";
+import { createAuditStore } from "../src/audit";
+import { loadConfig } from "../src/config";
+import { createDatabase } from "../src/db/client";
+import {
+ auditEvents,
+ mcpServers,
+ pendingAuthorizations,
+} from "../src/db/schema";
+import type { ConnectedAppBroker, GrantState } from "../src/plugins/broker";
+import type { AccountRef } from "../src/plugins/shared-accounts";
+import { createPluginStore } from "../src/plugins/store";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { testEnvironment } from "./support/environment";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+const suite = randomUUID().slice(0, 8);
+let counter = 0;
+
+type Provider = "arcade" | "composio";
+let calls: { toolkit: string; account: AccountRef }[] = [];
+let mints: string[] = [];
+let answer: (q: {
+ toolkit: string;
+ account: AccountRef;
+}) => Promise = async () => [];
+
+const recordGrants: ConnectedAppBroker["grantsFor"] = async (q) => {
+ calls.push(q);
+ return answer(q);
+};
+
+/** Declares the correlated flow and mints an authorization id: Arcade's shape. */
+const arcadeBroker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ authProviderOf: async () => "google",
+ authorizations: {
+ status: async () => ({ status: "pending", userId: null, scopes: [] }),
+ confirmReturn: async () => ({ mismatch: true }),
+ },
+ grantsFor: recordGrants,
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async (request) => {
+ mints.push(request.toolkit);
+ return {
+ redirectUrl: "https://vendor.example/consent?bearer=1",
+ authorizationId: "ar_1",
+ };
+ },
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => null,
+};
+
+/** Declares no flow, mints no authorization id, and still answers `grantsFor`. */
+const composioBroker: ConnectedAppBroker = {
+ grantIsLocal: false,
+ authProviderOf: async () => "google",
+ authorizations: null,
+ grantsFor: recordGrants,
+ listApps: async () => [],
+ ensureAuthConfig: async () => "standing",
+ deleteAuthConfig: async () => {},
+ authorize: async (request) => {
+ mints.push(request.toolkit);
+ return {
+ redirectUrl: "https://vendor.example/consent?bearer=1",
+ authorizationId: null,
+ };
+ },
+ isConnected: async () => true,
+ revoke: async () => true,
+ connectionFields: async () => [],
+ connectWithFields: async () => ({ accountId: "ca_1" }),
+ revokeAccount: async () => {},
+ accountName: async () => null,
+};
+
+const brokerFor = (provider: Provider) =>
+ provider === "arcade" ? arcadeBroker : composioBroker;
+
+function build(who: string, role: "admin" | "user", provider: Provider) {
+ const broker = brokerFor(provider);
+ const auditStore = createAuditStore(database);
+ const pluginStore = createPluginStore({
+ brokerProvider: provider,
+ database,
+ auditStore,
+ credentials: {} as never,
+ encryptionKey: "x".repeat(44),
+ policy: () => ({ mode: "enforce", deny: [], allow: ["true"] }),
+ broker,
+ deploymentId: `acme-${suite}`,
+ });
+ const args = new Array(createApp.length) as unknown as Parameters<
+ typeof createApp
+ >;
+ args[0] = loadConfig(
+ testEnvironment({ OPENBOT_APP_URL: "https://app.example" }),
+ );
+ args[1] = {
+ handler: () => new Response(null, { status: 204 }),
+ api: {
+ getSession: async () => ({
+ user: { id: who, email: `${who}@example.test`, name: "Person" },
+ }),
+ },
+ } as never;
+ args[2] = { rolesForUser: async () => [role] } as never;
+ args[12] = auditStore;
+ args[14] = pluginStore;
+ args[29] = { broker, provider };
+ return createApp(...args);
+}
+
+const ids: string[] = [];
+const toolkits: string[] = [];
+const viewers: string[] = [];
+
+/** This case's own names. Nothing another case writes can share one. */
+async function run(mode: "personal" | "shared", provider: Provider = "arcade") {
+ counter += 1;
+ const tag = `${suite}${counter}`;
+ const names = {
+ id: `grants-${tag}`,
+ toolkit: `Gmail${tag}`,
+ viewer: `viewer-${tag}`,
+ sharedIdentity: `openbot-deployment:acme:${tag}`,
+ };
+ ids.push(names.id);
+ toolkits.push(names.toolkit);
+ viewers.push(names.viewer);
+ await database.insert(mcpServers).values({
+ id: names.id,
+ title: "Gmail",
+ vendor: provider === "arcade" ? "Arcade" : "Composio",
+ url: `${provider}://${names.toolkit}`,
+ provenance: provider,
+ authScheme: "OAUTH2",
+ accountMode: mode,
+ sharedVendorUserId: mode === "shared" ? names.sharedIdentity : null,
+ });
+ calls = [];
+ mints = [];
+ answer = async () => [];
+ return names;
+}
+
+const grants = (
+ who: string,
+ role: "admin" | "user",
+ id: string,
+ provider: Provider = "arcade",
+) =>
+ build(who, role, provider).request(
+ `http://openbot.test/api/plugins/servers/${id}/grants`,
+ );
+
+afterAll(async () => {
+ await database.delete(mcpServers).where(inArray(mcpServers.id, ids));
+ await database.$client.close();
+});
+
+describe("whose identity is asked about", () => {
+ test("a personal app asks upstream as the viewer", async () => {
+ const n = await run("personal");
+ answer = async () => [
+ {
+ action: "Gmail.ListEmails",
+ state: "authorized",
+ scopes: ["a"],
+ authorization: "held",
+ },
+ ];
+ const response = await grants(n.viewer, "user", n.id);
+ expect(response.status).toBe(200);
+ const mine = calls.filter((c) => c.toolkit === n.toolkit);
+ expect(mine.map((c) => c.account)).toEqual([
+ { holder: "person", userId: n.viewer, vendorUserId: n.viewer },
+ ]);
+ });
+
+ test("a shared app asks upstream as the app's identity, never the viewer", async () => {
+ const n = await run("shared");
+ const response = await grants(n.viewer, "admin", n.id);
+ expect(response.status).toBe(200);
+ const mine = calls.filter((c) => c.toolkit === n.toolkit);
+ expect(mine.map((c) => c.account)).toEqual([
+ { holder: "deployment", vendorUserId: n.sharedIdentity },
+ ]);
+ expect(JSON.stringify(mine)).not.toContain(n.viewer);
+ });
+
+ test("a non-admin reads a shared app's status (200) but is refused the mint (403)", async () => {
+ const n = await run("shared");
+ const read = await grants(n.viewer, "user", n.id);
+ expect(read.status).toBe(200);
+ expect(
+ calls.filter((c) => c.toolkit === n.toolkit).map((c) => c.account),
+ ).toEqual([{ holder: "deployment", vendorUserId: n.sharedIdentity }]);
+
+ const mint = await build(n.viewer, "user", "arcade").request(
+ `http://openbot.test/api/plugins/servers/${n.id}/authorize`,
+ {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ action: "Gmail.ListEmails" }),
+ },
+ );
+ expect(mint.status).toBe(403);
+ // Scoped to this case's own app and viewer: nothing was minted or recorded for them.
+ expect(mints.filter((t) => t === n.toolkit)).toEqual([]);
+ const attempts = await database
+ .select()
+ .from(pendingAuthorizations)
+ .where(inArray(pendingAuthorizations.actorId, [n.viewer]));
+ expect(attempts).toEqual([]);
+ const trail = await database
+ .select()
+ .from(auditEvents)
+ .where(inArray(auditEvents.targetId, [n.toolkit, n.id]));
+ expect(trail.filter((e) => e.eventType.includes("authoriz"))).toEqual([]);
+ });
+});
+
+describe("what the states say", () => {
+ test("blocked only by a deployment secret: needs-operator, naming it", async () => {
+ const n = await run("personal");
+ answer = async () => [
+ {
+ action: "Github.ListRepos",
+ state: "needs-operator",
+ missing: ["GITHUB_SERVER_URL"],
+ },
+ ];
+ const body = (await (await grants(n.viewer, "user", n.id)).json()) as {
+ actions: GrantState[];
+ };
+ expect(body.actions).toEqual([
+ {
+ action: "Github.ListRepos",
+ state: "needs-operator",
+ missing: ["GITHUB_SERVER_URL"],
+ },
+ ]);
+ });
+
+ test("blocked by consent: needs-consent carrying its scopes", async () => {
+ const n = await run("personal");
+ answer = async () => [
+ {
+ action: "Gmail.SendEmail",
+ state: "needs-consent",
+ scopes: ["gmail.send"],
+ },
+ ];
+ const body = (await (await grants(n.viewer, "user", n.id)).json()) as {
+ actions: GrantState[];
+ };
+ expect(body.actions).toEqual([
+ {
+ action: "Gmail.SendEmail",
+ state: "needs-consent",
+ scopes: ["gmail.send"],
+ },
+ ]);
+ });
+
+ test("a listing that cannot be read is unknown, never needs-consent", async () => {
+ const n = await run("personal");
+ answer = async () => {
+ throw new Error(`vendor said no: token gho_secret_${suite}`);
+ };
+ const response = await grants(n.viewer, "user", n.id);
+ expect(response.status).toBe(200);
+ const text = await response.text();
+ const body = JSON.parse(text) as { actions: GrantState[] };
+ expect(body.actions.length).toBeGreaterThan(0);
+ for (const entry of body.actions) expect(entry.state).toBe("unknown");
+ expect(text).not.toContain("needs-consent");
+ expect(text).not.toContain("gho_secret");
+ });
+});
+
+test("a Composio deployment answers this route successfully", async () => {
+ const n = await run("personal", "composio");
+ answer = async () => [
+ { action: "GMAIL_FETCH", state: "needs-consent", scopes: [] },
+ ];
+ const response = await grants(n.viewer, "user", n.id, "composio");
+ expect(response.status).toBe(200);
+ expect(await response.json()).toEqual({
+ actions: [{ action: "GMAIL_FETCH", state: "needs-consent", scopes: [] }],
+ });
+ expect(
+ calls.filter((c) => c.toolkit === n.toolkit).map((c) => c.account),
+ ).toEqual([{ holder: "person", userId: n.viewer, vendorUserId: n.viewer }]);
+});
diff --git a/server/tests/arcade-journey.integration.test.ts b/server/tests/arcade-journey.integration.test.ts
new file mode 100644
index 000000000..37b41c8ce
--- /dev/null
+++ b/server/tests/arcade-journey.integration.test.ts
@@ -0,0 +1,1163 @@
+// server/tests/arcade-journey.integration.test.ts
+/**
+ * THE WHOLE ARCADE DEPLOYMENT, END TO END, THROUGH `createApp`.
+ *
+ * Select Arcade → browse the catalogue → enable an app → discover its actions → load the
+ * connected-accounts index → authorize one action → come back through the verifier → execute a
+ * call → disconnect. Every other Arcade suite checks one of those against its own factory; this one
+ * is the only place they are checked against EACH OTHER, which is where a vendor's name written by
+ * hand in one step and read by a parser in the next shows up.
+ *
+ * SO EVERY ASSERTION IS A PERSISTED FACT AND NEVER A STATUS CODE. A 201 says a route answered; what
+ * the journey turns on is the ROW it left — `arcade://GoogleCalendar-…` with its capitals intact,
+ * an `mcp_tools` row carrying the vendor's own `behavior`, a `pending_authorizations` row holding
+ * the resolved identity, a `brokered_connections` row stamped `arcade`, an audit entry naming the
+ * action and the scopes, and — on disconnect — the absence of all of them.
+ *
+ * AND THE SECOND HALF IS THE MERGE GATE. Two apps are switched to Shared, and the whole of what
+ * keeps them apart is that each holds its own minted identity: the two sit under ONE auth provider
+ * at the vendor, which is the configuration where a shared identity would be invisible until
+ * somebody pressed Disconnect on one app and lost the other. That is exercised THROUGH THE PRODUCT
+ * here — the mint the vendor was sent, the rows, the sibling preview and the disconnect — rather
+ * than inferred from the unique indexes.
+ *
+ * NOTHING IS DIALLED. The vendor is stubbed at the SDK PROTOTYPE, the seam every other Arcade
+ * adapter suite uses (`new Arcade({ apiKey })` opens no socket), so the REAL adapter and the REAL
+ * transport run: `listApps`, `authProviderOf`, `authorize`, `auth.status`, `auth.confirmUser`,
+ * `admin.userConnections` and `tools.execute` are this deployment's own code throughout, and only
+ * the wire is fake.
+ *
+ * THE FIXTURE IS EVIDENCE AND THE TOOLKITS ARE A CASE. `arcade-catalogue.json` is read for the one
+ * thing only a recording can be trusted about — the exact `metadata` a read, a write, a destructive
+ * and an UNANNOTATED action are published with — and nothing is appended to it. The toolkits themselves are
+ * constructed here, because the journey needs two apps sharing ONE auth provider, which is the
+ * live `Gmail`/`GoogleCalendar`-under-`google` shape the adapter's own docblock describes and which
+ * no single recorded page carries.
+ *
+ * WHY NO `execute` UNDER A SHARED APP'S IDENTITY IS DRIVEN HERE: coverage, not scope. The identity
+ * hop is already pinned by name at `gate-grant-is-local.test.ts:183` ("a vendor-held grant on a
+ * Shared app still runs as the app's shared identity"), which drives `connectionTokenFor`'s
+ * `broker && !broker.grantIsLocal -> resolved.ref.vendorUserId` with a deployment ref; that ref
+ * itself comes from `store.accountRefFor`, which this suite drives four times over. A shared execute
+ * would add only provider-agnostic gate machinery, owned by `shared-gated-mode.integration.test.ts`.
+ * If that test is deleted, this is the gap to re-check.
+ *
+ * `events.length === 1` IS A PER-ACTOR FACT, NOT A SYSTEM INVARIANT. `ON CONFLICT (actor_id)` proves
+ * one actor cannot overlap himself. Two DIFFERENT admins pressing Authorize on the same Shared app
+ * hold two attempt rows, both minting under the one shared vendor identity, and both completions
+ * run: the connection upsert is idempotent so the row stays single, but the trail takes two
+ * `mcp.account_connected` rows for one connection. That is consistent with D10 ("at most one
+ * authorization in flight PER ACTOR") and is arguably the honest trail.
+ *
+ * THE TWO `describe` BLOCKS ARE ORDER-COUPLED: the merge-gate describe relies on the first having
+ * enabled and connected both apps, and fails if run alone with `--test-name-pattern "two
+ * separately-shared"`. That failure is not a defect.
+ */
+import { afterAll, beforeAll, describe, expect, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import Arcade from "@arcadeai/arcadejs";
+import { and, eq, inArray } from "drizzle-orm";
+import { createApp } from "../src/app";
+import { createAuditStore } from "../src/audit";
+import { loadConfig } from "../src/config";
+import { createDatabase } from "../src/db/client";
+import {
+ agents,
+ auditEvents,
+ brokeredConnections,
+ mcpServers,
+ mcpTools,
+ pendingAuthorizations,
+ pluginGrants,
+} from "../src/db/schema";
+import { createAccountModeSwitch } from "../src/plugins/account-mode";
+import { useArcadeClient } from "../src/plugins/arcade";
+import { createArcadeClient } from "../src/plugins/arcade-adapter";
+import type { ConnectedAppBroker } from "../src/plugins/broker";
+import { createSharedUseStore } from "../src/plugins/shared-use-store";
+import { createPluginStore } from "../src/plugins/store";
+import fixture from "./fixtures/arcade-catalogue.json";
+import { TEST_POOL, testDatabaseUrl } from "./support/database";
+import { removePeople, seedPeople } from "./support/people";
+import { testEnvironment } from "./support/environment";
+
+const database = createDatabase(testDatabaseUrl(), TEST_POOL);
+
+const suite = randomUUID().slice(0, 8);
+/**
+ * Two toolkits whose CAPITALS CARRY MEANING. `GoogleCalendar-…` is the name a lowercasing step
+ * anywhere between the picker and the transport would destroy, and the suite suffix is what makes
+ * `audit_events` — which is append-only and cannot be cleaned between tests — answer about this run
+ * and no other.
+ */
+const MAIL = `Gmail-${suite}`;
+const DIARY = `GoogleCalendar-${suite}`;
+const MAIL_ROW = `arcade-${MAIL}`;
+const DIARY_ROW = `arcade-${DIARY}`;
+const SEND = `${MAIL}.SendEmail`;
+const DELETE_EVENT = `${DIARY}.DeleteEvent`;
+
+/**
+ * ONE AUTH PROVIDER FOR BOTH TOOLKITS, which is the whole point of the pair.
+ *
+ * `provider` is the BROKER (`arcade`); this is the VENDOR'S OAuth provider, and one Google
+ * configuration serves Gmail and GoogleCalendar both. Deliberately nothing like either toolkit
+ * name, so anything deriving one from the other fails here.
+ */
+const AUTH_PROVIDER = "google";
+
+/**
+ * AND THE CONFIGURATION OF IT THE CONNECTIONS ARE ACTUALLY HELD UNDER, which is a THIRD name.
+ *
+ * A tool listing's `provider_id` is the provider TYPE (`google`); a user-connection row's
+ * `provider_id` is the provider INSTANCE — an operator's configuration of that type, which is what
+ * the live project answers (`google-openbot`, `arcade-google`, `cpk-poc`). Spelled differently
+ * here on purpose: a journey whose vendor used one spelling on both sides would be green against
+ * an adapter that compares the two raw strings and finds nothing on every real deployment.
+ */
+const AUTH_PROVIDER_INSTANCE = `${AUTH_PROVIDER}-openbot`;
+
+const ADMIN = `arcade-journey-${suite}`;
+const BOT = `bot-${suite}`;
+const APP_URL = "https://app.example";
+/** A stand-in for the live `gho_…` the vendor's status reply carries beside the three fields. */
+const LIVE_TOKEN = `gho_live_access_token_${suite}`;
+
+const SCOPES: Record = {
+ [`${MAIL}.ListEmails`]: ["https://www.googleapis.com/auth/gmail.readonly"],
+ [SEND]: ["https://www.googleapis.com/auth/gmail.send"],
+ [`${MAIL}.TrashEmail`]: ["https://www.googleapis.com/auth/gmail.modify"],
+ [`${DIARY}.ListEvents`]: [
+ "https://www.googleapis.com/auth/calendar.readonly",
+ ],
+ [DELETE_EVENT]: ["https://www.googleapis.com/auth/calendar.events"],
+};
+
+/**
+ * The `metadata` shapes, READ OUT OF THE RECORDING rather than written here — the whole object and
+ * not just its `behavior`, so what the fold reads is the shape the vendor publishes. What a vendor
+ * actually publishes is the one thing a hand-written literal cannot be trusted about, and it is
+ * exactly what `classify` reads.
+ *
+ * `unclassified` IS A RECORDED `metadata: null`, AND IT IS WHAT MAKES THE COUNTS DISCRIMINATING. It
+ * classifies to the SAME closed `{ write, destructive }` verdict a genuinely destructive action
+ * does, so a toolkit whose every action is annotated cannot tell `classifiedCount` apart from
+ * `actionCount` — the coverage warning that exists to name unannotated actions would be green
+ * against a count that simply echoed the other.
+ */
+const recorded = fixture.tools as unknown as Record<
+ string,
+ { metadata: unknown }
+>;
+const METADATA = {
+ read: recorded.read.metadata,
+ write: recorded.write.metadata,
+ destructive: recorded.destructive.metadata,
+ unclassified: recorded.unclassified.metadata,
+};
+
+function action(
+ toolkit: string,
+ name: string,
+ metadata: unknown,
+): Record